swiftformat
This commit is contained in:
parent
961e639970
commit
4298062ca3
61
.swiftformat
61
.swiftformat
|
@ -1,18 +1,49 @@
|
|||
# version: 0.47.5
|
||||
|
||||
--indent 4 #indent
|
||||
--self init-only # redundantSelf
|
||||
--semicolons never # semicolons
|
||||
--stripunusedargs closure-only # unusedArguments
|
||||
--maxwidth 140 #wrap
|
||||
--assetliterals visual-width #wrap
|
||||
--wraparguments after-first # wrapArguments
|
||||
--wrapparameters after-first # wrapArguments
|
||||
--wrapcollections before-first # wrapArguments
|
||||
--wrapconditions after-first # wrapArguments
|
||||
--funcattributes prev-line # wrapAttributes
|
||||
--typeattributes prev-line # wrapAttributes
|
||||
--varattributes prev-line # wrapAttributes
|
||||
--swiftversion 5.5
|
||||
|
||||
--indent tab
|
||||
--tabwidth 4
|
||||
--xcodeindentation enabled
|
||||
--self init-only
|
||||
--semicolons never
|
||||
--stripunusedargs closure-only
|
||||
--maxwidth 140
|
||||
--assetliterals visual-width
|
||||
--wraparguments after-first
|
||||
--wrapparameters after-first
|
||||
--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
|
||||
* 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
|
||||
*/
|
||||
// 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
|
||||
import Stinsen
|
||||
|
@ -15,9 +14,11 @@ final class BasicAppSettingsCoordinator: NavigationCoordinatable {
|
|||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import Stinsen
|
||||
|
@ -15,14 +14,17 @@ final class ConnectToServerCoodinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \ConnectToServerCoodinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var userSignIn = makeUserSignIn
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.push)
|
||||
var userSignIn = makeUserSignIn
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import Stinsen
|
||||
|
@ -17,9 +16,11 @@ final class FilterCoordinator: NavigationCoordinatable {
|
|||
|
||||
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 parentId: String = ""
|
||||
|
||||
|
@ -29,7 +30,8 @@ final class FilterCoordinator: NavigationCoordinatable {
|
|||
self.parentId = parentId
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: parentId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -16,12 +15,18 @@ final class HomeCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \HomeCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.modal) var settings = makeSettings
|
||||
@Route(.push) var library = makeLibrary
|
||||
@Route(.push) var item = makeItem
|
||||
@Route(.modal) var modalItem = makeModalItem
|
||||
@Route(.modal) var modalLibrary = makeModalLibrary
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.modal)
|
||||
var settings = makeSettings
|
||||
@Route(.push)
|
||||
var library = makeLibrary
|
||||
@Route(.push)
|
||||
var item = makeItem
|
||||
@Route(.modal)
|
||||
var modalItem = makeModalItem
|
||||
@Route(.modal)
|
||||
var modalLibrary = makeModalLibrary
|
||||
|
||||
func makeSettings() -> NavigationViewCoordinator<SettingsCoordinator> {
|
||||
NavigationViewCoordinator(SettingsCoordinator())
|
||||
|
@ -36,14 +41,15 @@ final class HomeCoordinator: NavigationCoordinatable {
|
|||
}
|
||||
|
||||
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
||||
return NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -16,11 +15,16 @@ final class ItemCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \ItemCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var item = makeItem
|
||||
@Route(.push) var library = makeLibrary
|
||||
@Route(.modal) var itemOverview = makeItemOverview
|
||||
@Route(.fullScreen) var videoPlayer = makeVideoPlayer
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.push)
|
||||
var item = makeItem
|
||||
@Route(.push)
|
||||
var library = makeLibrary
|
||||
@Route(.modal)
|
||||
var itemOverview = makeItemOverview
|
||||
@Route(.fullScreen)
|
||||
var videoPlayer = makeVideoPlayer
|
||||
|
||||
let itemDto: BaseItemDto
|
||||
|
||||
|
@ -44,7 +48,8 @@ final class ItemCoordinator: NavigationCoordinatable {
|
|||
NavigationViewCoordinator(VideoPlayerCoordinator(viewModel: viewModel))
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
ItemNavigationView(item: itemDto)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 JellyfinAPI
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
import JellyfinAPI
|
||||
|
||||
final class ItemOverviewCoordinator: NavigationCoordinatable {
|
||||
|
||||
let stack = NavigationStack(initial: \ItemOverviewCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Root
|
||||
var start = makeStart
|
||||
|
||||
let item: BaseItemDto
|
||||
|
||||
|
@ -23,7 +23,8 @@ final class ItemOverviewCoordinator: NavigationCoordinatable {
|
|||
self.item = item
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
#if os(tvOS)
|
||||
EmptyView()
|
||||
#else
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -18,11 +17,16 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \LibraryCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var search = makeSearch
|
||||
@Route(.modal) var filter = makeFilter
|
||||
@Route(.push) var item = makeItem
|
||||
@Route(.modal) var modalItem = makeModalItem
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.push)
|
||||
var search = makeSearch
|
||||
@Route(.modal)
|
||||
var filter = makeFilter
|
||||
@Route(.push)
|
||||
var item = makeItem
|
||||
@Route(.modal)
|
||||
var modalItem = makeModalItem
|
||||
|
||||
let viewModel: LibraryViewModel
|
||||
let title: String
|
||||
|
@ -32,7 +36,8 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
|||
self.title = title
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
LibraryView(viewModel: self.viewModel, title: title)
|
||||
}
|
||||
|
||||
|
@ -51,6 +56,6 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
|||
}
|
||||
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
// 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
|
||||
import Stinsen
|
||||
|
@ -15,9 +14,12 @@ final class LibraryListCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \LibraryListCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var search = makeSearch
|
||||
@Route(.push) var library = makeLibrary
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.push)
|
||||
var search = makeSearch
|
||||
@Route(.push)
|
||||
var library = makeLibrary
|
||||
|
||||
let viewModel: LibraryListViewModel
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -15,12 +14,15 @@ import SwiftUI
|
|||
final class LiveTVChannelsCoordinator: NavigationCoordinatable {
|
||||
let stack = NavigationStack(initial: \LiveTVChannelsCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.modal) var modalItem = makeModalItem
|
||||
@Route(.fullScreen) var videoPlayer = makeVideoPlayer
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.modal)
|
||||
var modalItem = makeModalItem
|
||||
@Route(.fullScreen)
|
||||
var videoPlayer = makeVideoPlayer
|
||||
|
||||
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
||||
return NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||
}
|
||||
|
||||
func makeVideoPlayer(item: BaseItemDto) -> NavigationViewCoordinator<EmptyViewCoordinator> {
|
||||
|
@ -37,7 +39,8 @@ final class LiveTVChannelsCoordinator: NavigationCoordinatable {
|
|||
final class EmptyViewCoordinator: NavigationCoordinatable {
|
||||
let stack = NavigationStack(initial: \EmptyViewCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Root
|
||||
var start = makeStart
|
||||
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -16,8 +15,10 @@ final class LiveTVProgramsCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \LiveTVProgramsCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.fullScreen) var videoPlayer = makeVideoPlayer
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.fullScreen)
|
||||
var videoPlayer = makeVideoPlayer
|
||||
|
||||
func makeVideoPlayer(item: BaseItemDto) -> NavigationViewCoordinator<EmptyViewCoordinator> {
|
||||
// NavigationViewCoordinator(VideoPlayerCoordinator(item: item))
|
||||
|
|
|
@ -1,32 +1,35 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import SwiftUI
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
final class LiveTVTabCoordinator: TabCoordinatable {
|
||||
var child = TabChild(startingItems: [
|
||||
\LiveTVTabCoordinator.programs,
|
||||
\LiveTVTabCoordinator.channels,
|
||||
\LiveTVTabCoordinator.home
|
||||
\LiveTVTabCoordinator.home,
|
||||
])
|
||||
|
||||
@Route(tabItem: makeProgramsTab) var programs = makePrograms
|
||||
@Route(tabItem: makeChannelsTab) var channels = makeChannels
|
||||
@Route(tabItem: makeHomeTab) var home = makeHome
|
||||
@Route(tabItem: makeProgramsTab)
|
||||
var programs = makePrograms
|
||||
@Route(tabItem: makeChannelsTab)
|
||||
var channels = makeChannels
|
||||
@Route(tabItem: makeHomeTab)
|
||||
var home = makeHome
|
||||
|
||||
func makePrograms() -> NavigationViewCoordinator<LiveTVProgramsCoordinator> {
|
||||
return NavigationViewCoordinator(LiveTVProgramsCoordinator())
|
||||
NavigationViewCoordinator(LiveTVProgramsCoordinator())
|
||||
}
|
||||
|
||||
@ViewBuilder func makeProgramsTab(isActive: Bool) -> some View {
|
||||
@ViewBuilder
|
||||
func makeProgramsTab(isActive: Bool) -> some View {
|
||||
HStack {
|
||||
Image(systemName: "tv")
|
||||
Text("Programs")
|
||||
|
@ -34,10 +37,11 @@ final class LiveTVTabCoordinator: TabCoordinatable {
|
|||
}
|
||||
|
||||
func makeChannels() -> NavigationViewCoordinator<LiveTVChannelsCoordinator> {
|
||||
return NavigationViewCoordinator(LiveTVChannelsCoordinator())
|
||||
NavigationViewCoordinator(LiveTVChannelsCoordinator())
|
||||
}
|
||||
|
||||
@ViewBuilder func makeChannelsTab(isActive: Bool) -> some View {
|
||||
@ViewBuilder
|
||||
func makeChannelsTab(isActive: Bool) -> some View {
|
||||
HStack {
|
||||
Image(systemName: "square.grid.3x3")
|
||||
Text("Channels")
|
||||
|
@ -45,10 +49,11 @@ final class LiveTVTabCoordinator: TabCoordinatable {
|
|||
}
|
||||
|
||||
func makeHome() -> LiveTVHomeView {
|
||||
return LiveTVHomeView()
|
||||
LiveTVHomeView()
|
||||
}
|
||||
|
||||
@ViewBuilder func makeHomeTab(isActive: Bool) -> some View {
|
||||
@ViewBuilder
|
||||
func makeHomeTab(isActive: Bool) -> some View {
|
||||
HStack {
|
||||
Image(systemName: "house")
|
||||
Text("Home")
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Defaults
|
||||
|
@ -18,8 +17,10 @@ import WidgetKit
|
|||
final class MainCoordinator: NavigationCoordinatable {
|
||||
var stack: NavigationStack<MainCoordinator>
|
||||
|
||||
@Root var mainTab = makeMainTab
|
||||
@Root var serverList = makeServerList
|
||||
@Root
|
||||
var mainTab = makeMainTab
|
||||
@Root
|
||||
var serverList = makeServerList
|
||||
|
||||
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(didLogOut), name: SwiftfinNotificationCenter.Keys.didSignOut, 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)
|
||||
.sink { _ in
|
||||
|
@ -57,17 +59,20 @@ final class MainCoordinator: NavigationCoordinatable {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
@objc func didLogIn() {
|
||||
@objc
|
||||
func didLogIn() {
|
||||
LogManager.shared.log.info("Received `didSignIn` from SwiftfinNotificationCenter.")
|
||||
root(\.mainTab)
|
||||
}
|
||||
|
||||
@objc func didLogOut() {
|
||||
@objc
|
||||
func didLogOut() {
|
||||
LogManager.shared.log.info("Received `didSignOut` from SwiftfinNotificationCenter.")
|
||||
root(\.serverList)
|
||||
}
|
||||
|
||||
@objc func processDeepLink(_ notification: Notification) {
|
||||
@objc
|
||||
func processDeepLink(_ notification: Notification) {
|
||||
guard let deepLink = notification.object as? DeepLink else { return }
|
||||
if let coordinator = hasRoot(\.mainTab) {
|
||||
switch deepLink {
|
||||
|
@ -80,8 +85,10 @@ final class MainCoordinator: NavigationCoordinatable {
|
|||
}
|
||||
}
|
||||
|
||||
@objc func didChangeServerCurrentURI(_ notification: Notification) {
|
||||
guard let newCurrentServerState = notification.object as? SwiftfinStore.State.Server else { fatalError("Need to have new current login state server") }
|
||||
@objc
|
||||
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 }
|
||||
if newCurrentServerState.id == SessionManager.main.currentLogin.server.id {
|
||||
SessionManager.main.loginUser(server: newCurrentServerState, user: SessionManager.main.currentLogin.user)
|
||||
|
|
|
@ -1,44 +1,48 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import SwiftUI
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
final class MainTabCoordinator: TabCoordinatable {
|
||||
var child = TabChild(startingItems: [
|
||||
\MainTabCoordinator.home,
|
||||
\MainTabCoordinator.allMedia
|
||||
\MainTabCoordinator.allMedia,
|
||||
])
|
||||
|
||||
@Route(tabItem: makeHomeTab) var home = makeHome
|
||||
@Route(tabItem: makeAllMediaTab) var allMedia = makeAllMedia
|
||||
@Route(tabItem: makeHomeTab)
|
||||
var home = makeHome
|
||||
@Route(tabItem: makeAllMediaTab)
|
||||
var allMedia = makeAllMedia
|
||||
|
||||
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")
|
||||
L10n.home.text
|
||||
}
|
||||
|
||||
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")
|
||||
L10n.allMedia.text
|
||||
}
|
||||
|
||||
@ViewBuilder func customize(_ view: AnyView) -> some View {
|
||||
@ViewBuilder
|
||||
func customize(_ view: AnyView) -> some View {
|
||||
view.onAppear {
|
||||
AppURLHandler.shared.appURLState = .allowed
|
||||
// TODO: todo
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import Nuke
|
||||
|
@ -15,9 +14,12 @@ import SwiftUI
|
|||
final class MainCoordinator: NavigationCoordinatable {
|
||||
var stack = NavigationStack<MainCoordinator>(initial: \MainCoordinator.mainTab)
|
||||
|
||||
@Root var mainTab = makeMainTab
|
||||
@Root var serverList = makeServerList
|
||||
@Root var liveTV = makeLiveTV
|
||||
@Root
|
||||
var mainTab = makeMainTab
|
||||
@Root
|
||||
var serverList = makeServerList
|
||||
@Root
|
||||
var liveTV = makeLiveTV
|
||||
|
||||
@ViewBuilder
|
||||
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)
|
||||
}
|
||||
|
||||
@objc func didLogIn() {
|
||||
@objc
|
||||
func didLogIn() {
|
||||
LogManager.shared.log.info("Received `didSignIn` from NSNotificationCenter.")
|
||||
root(\.mainTab)
|
||||
}
|
||||
|
||||
@objc func didLogOut() {
|
||||
@objc
|
||||
func didLogOut() {
|
||||
LogManager.shared.log.info("Received `didSignOut` from NSNotificationCenter.")
|
||||
root(\.serverList)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import SwiftUI
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
final class MainTabCoordinator: TabCoordinatable {
|
||||
var child = TabChild(startingItems: [
|
||||
|
@ -17,20 +16,26 @@ final class MainTabCoordinator: TabCoordinatable {
|
|||
\MainTabCoordinator.tv,
|
||||
\MainTabCoordinator.movies,
|
||||
\MainTabCoordinator.other,
|
||||
\MainTabCoordinator.settings
|
||||
\MainTabCoordinator.settings,
|
||||
])
|
||||
|
||||
@Route(tabItem: makeHomeTab) var home = makeHome
|
||||
@Route(tabItem: makeTvTab) var tv = makeTv
|
||||
@Route(tabItem: makeMoviesTab) var movies = makeMovies
|
||||
@Route(tabItem: makeOtherTab) var other = makeOther
|
||||
@Route(tabItem: makeSettingsTab) var settings = makeSettings
|
||||
@Route(tabItem: makeHomeTab)
|
||||
var home = makeHome
|
||||
@Route(tabItem: makeTvTab)
|
||||
var tv = makeTv
|
||||
@Route(tabItem: makeMoviesTab)
|
||||
var movies = makeMovies
|
||||
@Route(tabItem: makeOtherTab)
|
||||
var other = makeOther
|
||||
@Route(tabItem: makeSettingsTab)
|
||||
var settings = makeSettings
|
||||
|
||||
func makeHome() -> NavigationViewCoordinator<HomeCoordinator> {
|
||||
return NavigationViewCoordinator(HomeCoordinator())
|
||||
NavigationViewCoordinator(HomeCoordinator())
|
||||
}
|
||||
|
||||
@ViewBuilder func makeHomeTab(isActive: Bool) -> some View {
|
||||
@ViewBuilder
|
||||
func makeHomeTab(isActive: Bool) -> some View {
|
||||
HStack {
|
||||
Image(systemName: "house")
|
||||
L10n.home.text
|
||||
|
@ -38,10 +43,11 @@ final class MainTabCoordinator: TabCoordinatable {
|
|||
}
|
||||
|
||||
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 {
|
||||
Image(systemName: "tv")
|
||||
Text("TV Shows")
|
||||
|
@ -49,10 +55,11 @@ final class MainTabCoordinator: TabCoordinatable {
|
|||
}
|
||||
|
||||
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 {
|
||||
Image(systemName: "film")
|
||||
Text("Movies")
|
||||
|
@ -60,10 +67,11 @@ final class MainTabCoordinator: TabCoordinatable {
|
|||
}
|
||||
|
||||
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 {
|
||||
Image(systemName: "folder")
|
||||
Text("Other")
|
||||
|
@ -71,10 +79,11 @@ final class MainTabCoordinator: TabCoordinatable {
|
|||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -16,8 +15,10 @@ final class MovieLibrariesCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \MovieLibrariesCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var library = makeLibrary
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.push)
|
||||
var library = makeLibrary
|
||||
|
||||
let viewModel: MovieLibrariesViewModel
|
||||
let title: String
|
||||
|
@ -27,7 +28,8 @@ final class MovieLibrariesCoordinator: NavigationCoordinatable {
|
|||
self.title = title
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
MovieLibrariesView(viewModel: self.viewModel, title: title)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
import JellyfinAPI
|
||||
|
||||
final class SearchCoordinator: NavigationCoordinatable {
|
||||
|
||||
let stack = NavigationStack(initial: \SearchCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var item = makeItem
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.push)
|
||||
var item = makeItem
|
||||
|
||||
let viewModel: LibrarySearchViewModel
|
||||
|
||||
|
@ -29,7 +30,8 @@ final class SearchCoordinator: NavigationCoordinatable {
|
|||
ItemCoordinator(item: item)
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
LibrarySearchView(viewModel: self.viewModel)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import Stinsen
|
||||
|
@ -15,7 +14,8 @@ final class ServerDetailCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \ServerDetailCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Root
|
||||
var start = makeStart
|
||||
|
||||
let viewModel: ServerDetailViewModel
|
||||
|
||||
|
@ -23,7 +23,8 @@ final class ServerDetailCoordinator: NavigationCoordinatable {
|
|||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
ServerDetailView(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import Stinsen
|
||||
|
@ -15,10 +14,14 @@ final class ServerListCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \ServerListCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var connectToServer = makeConnectToServer
|
||||
@Route(.push) var userList = makeUserList
|
||||
@Route(.modal) var basicAppSettings = makeBasicAppSettings
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.push)
|
||||
var connectToServer = makeConnectToServer
|
||||
@Route(.push)
|
||||
var userList = makeUserList
|
||||
@Route(.modal)
|
||||
var basicAppSettings = makeBasicAppSettings
|
||||
|
||||
func makeConnectToServer() -> ConnectToServerCoodinator {
|
||||
ConnectToServerCoodinator()
|
||||
|
@ -32,7 +35,8 @@ final class ServerListCoordinator: NavigationCoordinatable {
|
|||
NavigationViewCoordinator(BasicAppSettingsCoordinator())
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
ServerListView(viewModel: ServerListViewModel())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import Stinsen
|
||||
|
@ -15,25 +14,33 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \SettingsCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var serverDetail = makeServerDetail
|
||||
@Route(.push) var overlaySettings = makeOverlaySettings
|
||||
@Route(.push) var experimentalSettings = makeExperimentalSettings
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.push)
|
||||
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)
|
||||
ServerDetailView(viewModel: viewModel)
|
||||
}
|
||||
|
||||
@ViewBuilder func makeOverlaySettings() -> some View {
|
||||
@ViewBuilder
|
||||
func makeOverlaySettings() -> some View {
|
||||
OverlaySettingsView()
|
||||
}
|
||||
|
||||
@ViewBuilder func makeExperimentalSettings() -> some View {
|
||||
@ViewBuilder
|
||||
func makeExperimentalSettings() -> some View {
|
||||
ExperimentalSettingsView()
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
let viewModel = SettingsViewModel(server: SessionManager.main.currentLogin.server, user: SessionManager.main.currentLogin.user)
|
||||
SettingsView(viewModel: viewModel)
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -16,8 +15,10 @@ final class TVLibrariesCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \TVLibrariesCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var library = makeLibrary
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.push)
|
||||
var library = makeLibrary
|
||||
|
||||
let viewModel: TVLibrariesViewModel
|
||||
let title: String
|
||||
|
@ -27,7 +28,8 @@ final class TVLibrariesCoordinator: NavigationCoordinatable {
|
|||
self.title = title
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
TVLibrariesView(viewModel: self.viewModel, title: title)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import Stinsen
|
||||
|
@ -15,9 +14,12 @@ final class UserListCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \UserListCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var userSignIn = makeUserSignIn
|
||||
@Route(.push) var serverDetail = makeServerDetail
|
||||
@Root
|
||||
var start = makeStart
|
||||
@Route(.push)
|
||||
var userSignIn = makeUserSignIn
|
||||
@Route(.push)
|
||||
var serverDetail = makeServerDetail
|
||||
|
||||
let viewModel: UserListViewModel
|
||||
|
||||
|
@ -26,14 +28,15 @@ final class UserListCoordinator: NavigationCoordinatable {
|
|||
}
|
||||
|
||||
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 {
|
||||
return ServerDetailCoordinator(viewModel: .init(server: server))
|
||||
ServerDetailCoordinator(viewModel: .init(server: server))
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
UserListView(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import Stinsen
|
||||
|
@ -15,7 +14,8 @@ final class UserSignInCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \UserSignInCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Root
|
||||
var start = makeStart
|
||||
|
||||
let viewModel: UserSignInViewModel
|
||||
|
||||
|
@ -23,7 +23,8 @@ final class UserSignInCoordinator: NavigationCoordinatable {
|
|||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
UserSignInView(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Defaults
|
||||
import Foundation
|
||||
|
@ -17,7 +16,8 @@ final class VideoPlayerCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \VideoPlayerCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Root
|
||||
var start = makeStart
|
||||
|
||||
let viewModel: VideoPlayerViewModel
|
||||
|
||||
|
@ -25,7 +25,8 @@ final class VideoPlayerCoordinator: NavigationCoordinatable {
|
|||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
PreferenceUIHostingControllerView {
|
||||
VLCPlayerView(viewModel: self.viewModel)
|
||||
.navigationBarHidden(true)
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Defaults
|
||||
import Foundation
|
||||
|
@ -17,7 +16,8 @@ final class VideoPlayerCoordinator: NavigationCoordinatable {
|
|||
|
||||
let stack = NavigationStack(initial: \VideoPlayerCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Root
|
||||
var start = makeStart
|
||||
|
||||
let viewModel: VideoPlayerViewModel
|
||||
|
||||
|
@ -25,10 +25,10 @@ final class VideoPlayerCoordinator: NavigationCoordinatable {
|
|||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
@ViewBuilder
|
||||
func makeStart() -> some View {
|
||||
VLCPlayerView(viewModel: viewModel)
|
||||
.navigationBarHidden(true)
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -22,7 +21,7 @@ struct ErrorMessage: Identifiable {
|
|||
static let noShowErrorCode = -69420
|
||||
|
||||
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
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -13,7 +12,8 @@ import JellyfinAPI
|
|||
/**
|
||||
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".
|
||||
The specific kind of errors here should be created and surfaced from within JellyfinAPI on API calls.
|
||||
|
||||
Needs to be replaced
|
||||
*/
|
||||
|
||||
enum NetworkError: Error {
|
||||
|
@ -29,11 +29,11 @@ enum NetworkError: Error {
|
|||
|
||||
var errorMessage: ErrorMessage {
|
||||
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)
|
||||
case .HTTPURLError(let response, let displayMessage, let logConstructor):
|
||||
case let .HTTPURLError(response, displayMessage, 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)
|
||||
}
|
||||
}
|
||||
|
@ -62,14 +62,16 @@ enum NetworkError: Error {
|
|||
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
|
||||
var logMessage = "An error has occurred."
|
||||
var logConstructor = logConstructor
|
||||
|
||||
switch response {
|
||||
case .error(_, _, _, let err):
|
||||
case let .error(_, _, _, err):
|
||||
|
||||
// These codes are currently referenced from:
|
||||
// https://developer.apple.com/documentation/foundation/1508628-url_loading_system_error_codes
|
||||
|
@ -100,7 +102,9 @@ enum NetworkError: Error {
|
|||
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 logMessage = "An HTTP URL error has occurred"
|
||||
|
@ -119,14 +123,16 @@ enum NetworkError: Error {
|
|||
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
|
||||
var logMessage = "An error has occurred."
|
||||
var logConstructor = logConstructor
|
||||
|
||||
switch response {
|
||||
case .error(let code, _, _, _):
|
||||
case let .error(code, _, _, _):
|
||||
|
||||
// Generic HTTP status codes
|
||||
switch code {
|
||||
|
|
|
@ -1,29 +1,15 @@
|
|||
/*
|
||||
Copyright (c) 2018 Wolt Enterprises
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
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.
|
||||
*/
|
||||
//
|
||||
// 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 UIKit
|
||||
|
||||
extension UIImage {
|
||||
public convenience init?(blurHash: String, size: CGSize, punch: Float = 1) {
|
||||
public extension UIImage {
|
||||
convenience init?(blurHash: String, size: CGSize, punch: Float = 1) {
|
||||
guard blurHash.count >= 6 else { return nil }
|
||||
|
||||
let sizeFlag = String(blurHash[0]).decode83()
|
||||
|
@ -82,7 +68,8 @@ extension UIImage {
|
|||
|
||||
guard let provider = CGDataProvider(data: data) else { return nil }
|
||||
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)
|
||||
}
|
||||
|
@ -100,17 +87,15 @@ private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float
|
|||
let quantG = (value / 19) % 19
|
||||
let quantB = value % 19
|
||||
|
||||
let rgb = (
|
||||
signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
|
||||
let rgb = (signPow((Float(quantR) - 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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -124,7 +109,7 @@ private func sRGBToLinear<Type: BinaryInteger>(_ value: Type) -> Float {
|
|||
}
|
||||
|
||||
private let encodeCharacters: [String] = {
|
||||
return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) }
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) }
|
||||
}()
|
||||
|
||||
private let decodeCharacters: [String: Int] = {
|
||||
|
@ -149,7 +134,7 @@ extension String {
|
|||
|
||||
private extension String {
|
||||
subscript(offset: Int) -> Character {
|
||||
return self[index(startIndex, offsetBy: offset)]
|
||||
self[index(startIndex, offsetBy: offset)]
|
||||
}
|
||||
|
||||
subscript(bounds: CountableClosedRange<Int>) -> Substring {
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 UIKit
|
||||
|
||||
extension 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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
//
|
||||
// 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
|
||||
|
||||
|
@ -17,6 +18,6 @@ public extension Collection {
|
|||
///
|
||||
/// - Parameter index: index of element to access 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
|
||||
* 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
|
||||
*/
|
||||
// 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 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
|
||||
public static let systemFill = Color(UIColor.white)
|
||||
public static let secondarySystemFill = Color(UIColor.gray)
|
||||
public static let tertiarySystemFill = Color(UIColor.black)
|
||||
public static let lightGray = Color(UIColor.lightGray)
|
||||
static let systemFill = Color(UIColor.white)
|
||||
static let secondarySystemFill = Color(UIColor.gray)
|
||||
static let tertiarySystemFill = Color(UIColor.black)
|
||||
static let lightGray = Color(UIColor.lightGray)
|
||||
#else
|
||||
public static let systemFill = Color(UIColor.systemFill)
|
||||
public static let systemBackground = Color(UIColor.systemBackground)
|
||||
public static let secondarySystemFill = Color(UIColor.secondarySystemBackground)
|
||||
public static let tertiarySystemFill = Color(UIColor.tertiarySystemBackground)
|
||||
static let systemFill = Color(UIColor.systemFill)
|
||||
static let systemBackground = Color(UIColor.systemBackground)
|
||||
static let secondarySystemFill = Color(UIColor.secondarySystemBackground)
|
||||
static let tertiarySystemFill = Color(UIColor.tertiarySystemBackground)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
|
|
@ -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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
//
|
||||
// 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
|
||||
import SwiftUI
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Defaults
|
||||
import Foundation
|
||||
import JellyfinAPI
|
||||
|
||||
// MARK: PortraitImageStackable
|
||||
|
||||
extension BaseItemDto: PortraitImageStackable {
|
||||
public var portraitImageID: String {
|
||||
return id ?? "no id"
|
||||
id ?? "no id"
|
||||
}
|
||||
|
||||
public func imageURLContsructor(maxWidth: Int) -> URL {
|
||||
|
@ -45,12 +45,12 @@ extension BaseItemDto: PortraitImageStackable {
|
|||
}
|
||||
|
||||
public var blurHash: String {
|
||||
return self.getPrimaryImageBlurHash()
|
||||
self.getPrimaryImageBlurHash()
|
||||
}
|
||||
|
||||
public var failureInitials: String {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Defaults
|
||||
|
@ -16,32 +15,33 @@ extension BaseItemDto {
|
|||
func createVideoPlayerViewModel() -> AnyPublisher<VideoPlayerViewModel, Error> {
|
||||
let builder = DeviceProfileBuilder()
|
||||
// TODO: fix bitrate settings
|
||||
builder.setMaxBitrate(bitrate: 60000000)
|
||||
builder.setMaxBitrate(bitrate: 60_000_000)
|
||||
let profile = builder.buildProfile()
|
||||
|
||||
let playbackInfo = PlaybackInfoDto(userId: SessionManager.main.currentLogin.user.id,
|
||||
maxStreamingBitrate: 60000000,
|
||||
maxStreamingBitrate: 60_000_000,
|
||||
startTimeTicks: self.userData?.playbackPositionTicks ?? 0,
|
||||
deviceProfile: profile,
|
||||
autoOpenLiveStream: true)
|
||||
|
||||
return MediaInfoAPI.getPostedPlaybackInfo(itemId: self.id!,
|
||||
userId: SessionManager.main.currentLogin.user.id,
|
||||
maxStreamingBitrate: 60000000,
|
||||
maxStreamingBitrate: 60_000_000,
|
||||
startTimeTicks: self.userData?.playbackPositionTicks ?? 0,
|
||||
autoOpenLiveStream: true,
|
||||
playbackInfoDto: playbackInfo)
|
||||
.map({ response -> VideoPlayerViewModel in
|
||||
.map { response -> VideoPlayerViewModel in
|
||||
let mediaSource = response.mediaSources!.first!
|
||||
|
||||
let audioStreams = mediaSource.mediaStreams?.filter({ $0.type == .audio }) ?? []
|
||||
let subtitleStreams = mediaSource.mediaStreams?.filter({ $0.type == .subtitle }) ?? []
|
||||
let audioStreams = mediaSource.mediaStreams?.filter { $0.type == .audio } ?? []
|
||||
let subtitleStreams = mediaSource.mediaStreams?.filter { $0.type == .subtitle } ?? []
|
||||
|
||||
let defaultAudioStream = audioStreams.first(where: { $0.index! == mediaSource.defaultAudioStreamIndex! })
|
||||
|
||||
let defaultSubtitleStream = subtitleStreams.first(where: { $0.index! == mediaSource.defaultSubtitleStreamIndex ?? -1 })
|
||||
|
||||
// MARK: Stream
|
||||
|
||||
var streamURL = URLComponents(string: SessionManager.main.currentLogin.server.currentURI)!
|
||||
|
||||
let streamType: ServerStreamType
|
||||
|
@ -61,7 +61,7 @@ extension BaseItemDto {
|
|||
|
||||
// MARK: VidoPlayerViewModel Creation
|
||||
|
||||
var subtitle: String? = nil
|
||||
var subtitle: String?
|
||||
|
||||
// MARK: Attach media content to self
|
||||
|
||||
|
@ -103,7 +103,7 @@ extension BaseItemDto {
|
|||
shouldShowAutoPlay: shouldShowAutoPlay)
|
||||
|
||||
return videoPlayerViewModel
|
||||
})
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -187,7 +186,8 @@ public extension BaseItemDto {
|
|||
|
||||
func getLiveProgressPercentage() -> Double {
|
||||
if let startDate = self.startDate,
|
||||
let endDate = self.endDate {
|
||||
let endDate = self.endDate
|
||||
{
|
||||
let start = startDate.timeIntervalSinceReferenceDate
|
||||
let end = endDate.timeIntervalSinceReferenceDate
|
||||
let now = Date().timeIntervalSinceReferenceDate
|
||||
|
@ -276,16 +276,18 @@ public extension BaseItemDto {
|
|||
}
|
||||
|
||||
if let mediaStreams = mediaStreams {
|
||||
let audioStreams = mediaStreams.filter({ $0.type == .audio })
|
||||
let subtitleStreams = mediaStreams.filter({ $0.type == .subtitle })
|
||||
let audioStreams = mediaStreams.filter { $0.type == .audio }
|
||||
let subtitleStreams = mediaStreams.filter { $0.type == .subtitle }
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
//
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -12,6 +13,7 @@ import UIKit
|
|||
extension BaseItemPerson {
|
||||
|
||||
// MARK: Get Image
|
||||
|
||||
func getImage(baseURL: String, maxWidth: Int) -> URL {
|
||||
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
|
||||
|
||||
|
@ -45,7 +47,8 @@ extension BaseItemPerson {
|
|||
let split = role.split(separator: "/")
|
||||
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
|
||||
|
||||
|
@ -59,39 +62,41 @@ extension BaseItemPerson {
|
|||
}
|
||||
|
||||
// MARK: PortraitImageStackable
|
||||
|
||||
extension BaseItemPerson: PortraitImageStackable {
|
||||
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 {
|
||||
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 {
|
||||
return self.name ?? ""
|
||||
self.name ?? ""
|
||||
}
|
||||
|
||||
public var subtitle: String? {
|
||||
return self.firstRole()
|
||||
self.firstRole()
|
||||
}
|
||||
|
||||
public var blurHash: String {
|
||||
return self.getBlurHash()
|
||||
self.getBlurHash()
|
||||
}
|
||||
|
||||
public var failureInitials: String {
|
||||
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)
|
||||
}
|
||||
|
||||
public var showTitle: Bool {
|
||||
return true
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: DiplayedType
|
||||
|
||||
extension BaseItemPerson {
|
||||
|
||||
// Only displayed person types.
|
||||
|
@ -103,7 +108,7 @@ extension BaseItemPerson {
|
|||
case producer = "Producer"
|
||||
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
@ -18,6 +17,6 @@ struct JellyfinAPIError: Error {
|
|||
}
|
||||
|
||||
var localizedDescription: String {
|
||||
return message
|
||||
message
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
||||
extension NameGuidPair: PillStackable {
|
||||
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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
//
|
||||
// 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
|
||||
import SwiftUI
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 UIKit
|
||||
|
||||
extension UIDevice {
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
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
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
|
@ -70,8 +78,9 @@ internal enum L10n {
|
|||
internal static let home = L10n.tr("Localizable", "home")
|
||||
/// Latest %@
|
||||
internal static func latestWithString(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "latestWithString", String(describing: p1))
|
||||
L10n.tr("Localizable", "latestWithString", String(describing: p1))
|
||||
}
|
||||
|
||||
/// Library
|
||||
internal static let library = L10n.tr("Localizable", "library")
|
||||
/// Light
|
||||
|
@ -84,8 +93,9 @@ internal enum L10n {
|
|||
internal static let login = L10n.tr("Localizable", "login")
|
||||
/// Login to %@
|
||||
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
|
||||
internal static let moreLikeThis = L10n.tr("Localizable", "moreLikeThis")
|
||||
/// Next Up
|
||||
|
@ -96,16 +106,18 @@ internal enum L10n {
|
|||
internal static let noResults = L10n.tr("Localizable", "noResults")
|
||||
/// Type: %@ not implemented yet :(
|
||||
internal static func notImplementedYetWithType(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "notImplementedYetWithType", String(describing: p1))
|
||||
L10n.tr("Localizable", "notImplementedYetWithType", String(describing: p1))
|
||||
}
|
||||
|
||||
/// Ok
|
||||
internal static let ok = L10n.tr("Localizable", "ok")
|
||||
/// Other User
|
||||
internal static let otherUser = L10n.tr("Localizable", "otherUser")
|
||||
/// Page %1$@ of %2$@
|
||||
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
|
||||
internal static let password = L10n.tr("Localizable", "password")
|
||||
/// Play
|
||||
|
@ -122,8 +134,9 @@ internal enum L10n {
|
|||
internal static let search = L10n.tr("Localizable", "search")
|
||||
/// S%1$@:E%2$@
|
||||
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
|
||||
internal static let seasons = L10n.tr("Localizable", "seasons")
|
||||
/// See All
|
||||
|
@ -132,16 +145,18 @@ internal enum L10n {
|
|||
internal static let selectCastDestination = L10n.tr("Localizable", "selectCastDestination")
|
||||
/// Server %s already exists. Add new URL?
|
||||
internal static func serverAlreadyExistsPrompt(_ p1: UnsafePointer<CChar>) -> String {
|
||||
return L10n.tr("Localizable", "serverAlreadyExistsPrompt", p1)
|
||||
L10n.tr("Localizable", "serverAlreadyExistsPrompt", p1)
|
||||
}
|
||||
|
||||
/// Server Information
|
||||
internal static let serverInformation = L10n.tr("Localizable", "serverInformation")
|
||||
/// Server URL
|
||||
internal static let serverURL = L10n.tr("Localizable", "serverURL")
|
||||
/// Signed in as %@
|
||||
internal static func signedInAsWithString(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "signedInAsWithString", String(describing: p1))
|
||||
L10n.tr("Localizable", "signedInAsWithString", String(describing: p1))
|
||||
}
|
||||
|
||||
/// Sort by
|
||||
internal static let sortBy = L10n.tr("Localizable", "sortBy")
|
||||
/// STUDIO
|
||||
|
@ -169,6 +184,7 @@ internal enum L10n {
|
|||
/// Your Favorites
|
||||
internal static let yourFavorites = L10n.tr("Localizable", "yourFavorites")
|
||||
}
|
||||
|
||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
|
||||
|
@ -191,4 +207,5 @@ private final class BundleToken {
|
|||
#endif
|
||||
}()
|
||||
}
|
||||
|
||||
// swiftlint:enable convenience_type
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Defaults
|
||||
import SwiftUI
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -21,5 +20,4 @@ struct DetailItem {
|
|||
|
||||
let baseItem: BaseItemDto
|
||||
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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
//
|
||||
// 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
|
||||
//
|
||||
|
||||
// lol can someone buy me a coffee this took forever :|
|
||||
|
||||
|
@ -47,25 +48,34 @@ class DeviceProfileBuilder {
|
|||
|
||||
// Build direct play profiles
|
||||
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)
|
||||
if supportsFeature(minimumSupported: .A8X) {
|
||||
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 {
|
||||
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?
|
||||
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?
|
||||
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
|
||||
|
@ -75,29 +85,41 @@ class DeviceProfileBuilder {
|
|||
// Device supports Dolby Digital (AC3, EAC3)
|
||||
if supportsFeature(minimumSupported: .A8X) {
|
||||
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 {
|
||||
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?
|
||||
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] = []
|
||||
|
||||
let h264CodecConditions: [ProfileCondition] = [
|
||||
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: .notEquals, property: .isInterlaced, value: "true", isRequired: false)]
|
||||
ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false),
|
||||
]
|
||||
let hevcCodecConditions: [ProfileCondition] = [
|
||||
ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", 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: .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"))
|
||||
|
||||
|
@ -124,13 +146,37 @@ class DeviceProfileBuilder {
|
|||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
//
|
||||
|
||||
// https://www.hackingwithswift.com/quick-start/swiftui/how-to-detect-device-rotation
|
||||
import Foundation
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Defaults
|
||||
import Foundation
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Defaults
|
||||
import UIKit
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Defaults
|
||||
import Foundation
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
|
|
@ -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
|
||||
* 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
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
|
|
@ -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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
//
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 UIKit
|
||||
import Defaults
|
||||
import UIKit
|
||||
|
||||
enum VideoPlayerJumpLength: Int32, CaseIterable, Defaults.Serializable {
|
||||
case thirty = 30
|
||||
|
@ -17,11 +16,11 @@ enum VideoPlayerJumpLength: Int32, CaseIterable, Defaults.Serializable {
|
|||
case five = 5
|
||||
|
||||
var label: String {
|
||||
return "\(self.rawValue) seconds"
|
||||
"\(self.rawValue) seconds"
|
||||
}
|
||||
|
||||
var shortLabel: String {
|
||||
return "\(self.rawValue)s"
|
||||
"\(self.rawValue)s"
|
||||
}
|
||||
|
||||
var forwardImageLabel: String {
|
||||
|
|
|
@ -1,12 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Created by Noah Kamara
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
@ -14,7 +12,7 @@ public class ServerDiscovery {
|
|||
public struct ServerLookupResponse: Codable, Hashable, Identifiable {
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
return hasher.combine(id)
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
private let address: String
|
||||
|
@ -24,6 +22,7 @@ public class ServerDiscovery {
|
|||
public var url: URL {
|
||||
URL(string: self.address)!
|
||||
}
|
||||
|
||||
public var host: String {
|
||||
let components = URLComponents(string: self.address)
|
||||
if let host = components?.host {
|
||||
|
@ -50,11 +49,9 @@ public class ServerDiscovery {
|
|||
private let broadcastConn: UDPBroadcastConnection
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
//
|
||||
/*
|
||||
* 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/.
|
||||
*
|
||||
* Created by Gunter Hager on 10.02.16.
|
||||
* Copyright © 2016 Gunter Hager. All rights reserved.
|
||||
*/
|
||||
// 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
|
||||
import Darwin
|
||||
import Foundation
|
||||
|
||||
// Addresses
|
||||
|
||||
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.
|
||||
open class UDPBroadcastConnection {
|
||||
|
@ -38,7 +36,7 @@ open class UDPBroadcastConnection {
|
|||
var responseSource: DispatchSourceRead?
|
||||
|
||||
/// 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
|
||||
var shouldBeBound: Bool = false
|
||||
|
@ -56,13 +54,11 @@ open class UDPBroadcastConnection {
|
|||
/// - errorHandler: Handler that gets called when an error occurs.
|
||||
/// - Throws: Throws a `ConnectionError` if an error occurs.
|
||||
public init(port: UInt16, bindIt: Bool = false, handler: ReceiveHandler?, errorHandler: ErrorHandler?) throws {
|
||||
self.address = sockaddr_in(
|
||||
sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size),
|
||||
self.address = sockaddr_in(sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size),
|
||||
sin_family: sa_family_t(AF_INET),
|
||||
sin_port: UDPBroadcastConnection.htonsPort(port: port),
|
||||
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.errorHandler = errorHandler
|
||||
|
@ -137,7 +133,8 @@ open class UDPBroadcastConnection {
|
|||
let UDPSocket = Int32(source.handle)
|
||||
|
||||
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 {
|
||||
|
@ -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 {
|
||||
// debugPrint("Failed to get the address and port from the socket address received from recvfrom")
|
||||
self.closeConnection()
|
||||
|
@ -174,7 +175,6 @@ open class UDPBroadcastConnection {
|
|||
self.errorHandler?(ConnectionError.underlying(error: error))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
newResponseSource.resume()
|
||||
|
@ -202,7 +202,7 @@ open class UDPBroadcastConnection {
|
|||
guard let source = responseSource else { return }
|
||||
let UDPSocket = Int32(source.handle)
|
||||
let socketLength = socklen_t(address.sin_len)
|
||||
try data.withUnsafeBytes { (broadcastMessage) in
|
||||
try data.withUnsafeBytes { broadcastMessage in
|
||||
let broadcastMessageLength = data.count
|
||||
let sent = withUnsafeMutablePointer(to: &address) { pointer -> Int in
|
||||
let memory = UnsafeRawPointer(pointer).bindMemory(to: sockaddr.self, capacity: 1)
|
||||
|
@ -282,9 +282,8 @@ open class UDPBroadcastConnection {
|
|||
}
|
||||
|
||||
fileprivate class func ntohs(value: CUnsignedShort) -> CUnsignedShort {
|
||||
return (value << 8) + (value >> 8)
|
||||
(value << 8) + (value >> 8)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Created by Gunter Hager on 25.03.19.
|
||||
|
@ -312,5 +311,4 @@ public extension UDPBroadcastConnection {
|
|||
// Underlying
|
||||
case underlying(error: Error)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import Puppy
|
||||
|
@ -34,7 +33,7 @@ class LogManager {
|
|||
}
|
||||
|
||||
func logFileURL() -> URL {
|
||||
return self.getDocumentsDirectory().appendingPathComponent("logs.txt")
|
||||
self.getDocumentsDirectory().appendingPathComponent("logs.txt")
|
||||
}
|
||||
|
||||
func getDocumentsDirectory() -> URL {
|
||||
|
@ -49,7 +48,8 @@ class LogManager {
|
|||
class LogFormatter: LogFormattable {
|
||||
func formatMessage(_ level: LogLevel, message: String, tag: String, function: 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: "")
|
||||
return " [\(level.emoji) \(level)] \(file)#\(line):\(function) \(message)"
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import CoreData
|
||||
|
@ -18,8 +17,8 @@ import UIKit
|
|||
typealias CurrentLogin = (server: SwiftfinStore.State.Server, user: SwiftfinStore.State.User)
|
||||
|
||||
// MARK: NewSessionManager
|
||||
final class SessionManager {
|
||||
|
||||
final class SessionManager {
|
||||
|
||||
// MARK: currentLogin
|
||||
|
||||
|
@ -30,12 +29,15 @@ final class SessionManager {
|
|||
static let main = SessionManager()
|
||||
|
||||
// MARK: init
|
||||
|
||||
private init() {
|
||||
if let lastUserID = Defaults[.lastServerUserID],
|
||||
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 }
|
||||
|
||||
JellyfinAPI.basePath = server.currentURI
|
||||
|
@ -45,20 +47,23 @@ final class SessionManager {
|
|||
}
|
||||
|
||||
// MARK: fetchServers
|
||||
|
||||
func fetchServers() -> [SwiftfinStore.State.Server] {
|
||||
let servers = try! SwiftfinStore.dataStack.fetchAll(From<SwiftfinStore.Models.StoredServer>())
|
||||
return servers.map({ $0.state })
|
||||
return servers.map(\.state)
|
||||
}
|
||||
|
||||
// MARK: fetchUsers
|
||||
|
||||
func fetchUsers(for server: SwiftfinStore.State.Server) -> [SwiftfinStore.State.User] {
|
||||
guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
||||
Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id))
|
||||
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
|
||||
|
||||
// Connects to a server at the given uri, storing if successful
|
||||
func connectToServer(with uri: String) -> AnyPublisher<SwiftfinStore.State.Server, Error> {
|
||||
var uriComponents = URLComponents(string: uri) ?? URLComponents()
|
||||
|
@ -76,7 +81,7 @@ final class SessionManager {
|
|||
JellyfinAPI.basePath = uri
|
||||
|
||||
return SystemAPI.getPublicSystemInfo()
|
||||
.tryMap({ response -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in
|
||||
.tryMap { response -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in
|
||||
|
||||
let transaction = SwiftfinStore.dataStack.beginUnsafe()
|
||||
let newServer = transaction.create(Into<SwiftfinStore.Models.StoredServer>())
|
||||
|
@ -96,30 +101,35 @@ final class SessionManager {
|
|||
|
||||
// Check for existing server on device
|
||||
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)
|
||||
}
|
||||
|
||||
return (newServer, transaction)
|
||||
})
|
||||
.handleEvents(receiveOutput: { (_, transaction) in
|
||||
}
|
||||
.handleEvents(receiveOutput: { _, transaction in
|
||||
try? transaction.commitAndWait()
|
||||
})
|
||||
.map({ (server, _) in
|
||||
return server.state
|
||||
})
|
||||
.map { server, _ in
|
||||
server.state
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
// MARK: addURIToServer publisher
|
||||
|
||||
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
|
||||
|
||||
let transaction = SwiftfinStore.dataStack.beginUnsafe()
|
||||
|
||||
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?")
|
||||
}
|
||||
|
||||
|
@ -128,24 +138,27 @@ final class SessionManager {
|
|||
|
||||
return (editServer, transaction)
|
||||
}
|
||||
.handleEvents(receiveOutput: { (_, transaction) in
|
||||
.handleEvents(receiveOutput: { _, transaction in
|
||||
try? transaction.commitAndWait()
|
||||
})
|
||||
.map({ (server, _) in
|
||||
return server.state
|
||||
})
|
||||
.map { server, _ in
|
||||
server.state
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
// MARK: setServerCurrentURI publisher
|
||||
|
||||
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
|
||||
|
||||
let transaction = SwiftfinStore.dataStack.beginUnsafe()
|
||||
|
||||
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?")
|
||||
}
|
||||
|
||||
|
@ -158,24 +171,27 @@ final class SessionManager {
|
|||
|
||||
return (editServer, transaction)
|
||||
}
|
||||
.handleEvents(receiveOutput: { (_, transaction) in
|
||||
.handleEvents(receiveOutput: { _, transaction in
|
||||
try? transaction.commitAndWait()
|
||||
})
|
||||
.map({ (server, _) in
|
||||
return server.state
|
||||
})
|
||||
.map { server, _ in
|
||||
server.state
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
// MARK: loginUser publisher
|
||||
|
||||
// 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: "")
|
||||
|
||||
JellyfinAPI.basePath = server.currentURI
|
||||
|
||||
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") }
|
||||
|
||||
|
@ -191,7 +207,9 @@ final class SessionManager {
|
|||
|
||||
// Check for existing user on device
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -200,15 +218,18 @@ final class SessionManager {
|
|||
newUser.accessToken = newAccessToken
|
||||
|
||||
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?") }
|
||||
|
||||
guard let editUserServer = transaction.edit(userServer) else { fatalError("Can't get proxy for existing object?") }
|
||||
editUserServer.users.insert(newUser)
|
||||
|
||||
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 ?? "")
|
||||
try? transaction.commitAndWait()
|
||||
|
||||
|
@ -221,13 +242,14 @@ final class SessionManager {
|
|||
currentLogin = (server: currentServer.state, user: currentUser.state)
|
||||
SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil)
|
||||
})
|
||||
.map({ (_, user, _) in
|
||||
return user.state
|
||||
})
|
||||
.map { _, user, _ in
|
||||
user.state
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
// MARK: loginUser
|
||||
|
||||
func loginUser(server: SwiftfinStore.State.Server, user: SwiftfinStore.State.User) {
|
||||
JellyfinAPI.basePath = server.currentURI
|
||||
Defaults[.lastServerUserID] = user.id
|
||||
|
@ -237,6 +259,7 @@ final class SessionManager {
|
|||
}
|
||||
|
||||
// MARK: logout
|
||||
|
||||
func logout() {
|
||||
currentLogin = nil
|
||||
JellyfinAPI.basePath = ""
|
||||
|
@ -246,6 +269,7 @@ final class SessionManager {
|
|||
}
|
||||
|
||||
// MARK: purge
|
||||
|
||||
func purge() {
|
||||
// Delete all servers
|
||||
let servers = fetchServers()
|
||||
|
@ -258,16 +282,20 @@ final class SessionManager {
|
|||
}
|
||||
|
||||
// MARK: delete user
|
||||
|
||||
func delete(user: SwiftfinStore.State.User) {
|
||||
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)
|
||||
}
|
||||
|
||||
// MARK: delete server
|
||||
|
||||
func delete(server: SwiftfinStore.State.Server) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 SwiftfinNotificationCenter {
|
||||
|
||||
static let main: NotificationCenter = {
|
||||
return NotificationCenter()
|
||||
NotificationCenter()
|
||||
}()
|
||||
|
||||
enum Keys {
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import CoreStore
|
||||
import Defaults
|
||||
import Foundation
|
||||
|
||||
enum SwiftfinStore {
|
||||
|
||||
// MARK: State
|
||||
|
||||
// Safe, copyable representations of their underlying CoreStoredObject
|
||||
// Relationships are represented by the related object's IDs or value
|
||||
enum State {
|
||||
|
@ -27,7 +27,9 @@ enum SwiftfinStore {
|
|||
let version: 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.currentURI = currentURI
|
||||
self.name = name
|
||||
|
@ -38,7 +40,7 @@ enum SwiftfinStore {
|
|||
}
|
||||
|
||||
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",
|
||||
name: "Johnny's Tree",
|
||||
id: "123abc",
|
||||
|
@ -62,7 +64,7 @@ enum SwiftfinStore {
|
|||
}
|
||||
|
||||
static var sample: User {
|
||||
return User(username: "JohnnyAppleseed",
|
||||
User(username: "JohnnyAppleseed",
|
||||
id: "123abc",
|
||||
serverID: "123abc",
|
||||
accessToken: "open-sesame")
|
||||
|
@ -71,6 +73,7 @@ enum SwiftfinStore {
|
|||
}
|
||||
|
||||
// MARK: Models
|
||||
|
||||
enum Models {
|
||||
|
||||
final class StoredServer: CoreStoreObject {
|
||||
|
@ -97,13 +100,13 @@ enum SwiftfinStore {
|
|||
var users: Set<StoredUser>
|
||||
|
||||
var state: State.Server {
|
||||
return State.Server(uris: uris,
|
||||
State.Server(uris: uris,
|
||||
currentURI: currentURI,
|
||||
name: name,
|
||||
id: id,
|
||||
os: os,
|
||||
version: version,
|
||||
usersIDs: users.map({ $0.id }))
|
||||
usersIDs: users.map(\.id))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,37 +148,51 @@ enum SwiftfinStore {
|
|||
}
|
||||
|
||||
// MARK: Errors
|
||||
|
||||
enum Errors {
|
||||
case existingServer(State.Server)
|
||||
case existingUser(State.User)
|
||||
}
|
||||
|
||||
// MARK: dataStack
|
||||
|
||||
static let dataStack: DataStack = {
|
||||
let schema = CoreStoreSchema(modelVersion: "V1",
|
||||
entities: [
|
||||
Entity<SwiftfinStore.Models.StoredServer>("Server"),
|
||||
Entity<SwiftfinStore.Models.StoredUser>("User"),
|
||||
Entity<SwiftfinStore.Models.StoredAccessToken>("AccessToken")
|
||||
Entity<SwiftfinStore.Models.StoredAccessToken>("AccessToken"),
|
||||
],
|
||||
versionLock: [
|
||||
"AccessToken": [0xa8c475e874494bb1, 0x79486e93449f0b3d, 0xa7dc4a0003541edb, 0x94183fae7580ef72],
|
||||
"Server": [0x936b46acd8e8f0e3, 0x59890d4d9f3f885f, 0x819cf7a4abf98b22, 0xe16125c5af885a06],
|
||||
"User": [0x845de08a74bc53ed, 0xe95a406a29f3a5d0, 0x9eda732821a15ea9, 0xb5afa531e41ce8a]
|
||||
"AccessToken": [
|
||||
0xA8C4_75E8_7449_4BB1,
|
||||
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)
|
||||
try! _dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "Swiftfin.sqlite",
|
||||
localStorageOptions: .recreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
try! _dataStack.addStorageAndWait(SQLiteStore(fileName: "Swiftfin.sqlite",
|
||||
localStorageOptions: .recreateStoreOnModelMismatch))
|
||||
return _dataStack
|
||||
}()
|
||||
}
|
||||
|
||||
// MARK: LocalizedError
|
||||
|
||||
extension SwiftfinStore.Errors: LocalizedError {
|
||||
|
||||
var title: String {
|
||||
|
@ -189,9 +206,9 @@ extension SwiftfinStore.Errors: LocalizedError {
|
|||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .existingServer(let server):
|
||||
case let .existingServer(server):
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Defaults
|
||||
import Foundation
|
||||
|
@ -15,11 +14,11 @@ extension SwiftfinStore {
|
|||
enum Defaults {
|
||||
|
||||
static let generalSuite: UserDefaults = {
|
||||
return UserDefaults(suiteName: "swiftfinstore-general-defaults")!
|
||||
UserDefaults(suiteName: "swiftfinstore-general-defaults")!
|
||||
}()
|
||||
|
||||
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 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 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)
|
||||
|
||||
// Customize settings
|
||||
|
@ -45,8 +45,10 @@ extension Defaults.Keys {
|
|||
// Video player / overlay settings
|
||||
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 videoPlayerJumpForward = Key<VideoPlayerJumpLength>("videoPlayerJumpForward", default: .fifteen, suite: SwiftfinStore.Defaults.generalSuite)
|
||||
static let videoPlayerJumpBackward = Key<VideoPlayerJumpLength>("videoPlayerJumpBackward", default: .fifteen, suite: SwiftfinStore.Defaults.generalSuite)
|
||||
static let videoPlayerJumpForward = Key<VideoPlayerJumpLength>("videoPlayerJumpForward", default: .fifteen,
|
||||
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 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)
|
||||
|
||||
// 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
|
||||
struct Experimental {
|
||||
static let syncSubtitleStateWithAdjacent = Key<Bool>("experimental.syncSubtitleState", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
||||
enum Experimental {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 SwiftUI
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -18,16 +17,20 @@ struct AddServerURIPayload: Identifiable {
|
|||
let uri: String
|
||||
|
||||
var id: String {
|
||||
return server.id.appending(uri)
|
||||
server.id.appending(uri)
|
||||
}
|
||||
}
|
||||
|
||||
final class ConnectToServerViewModel: ViewModel {
|
||||
|
||||
@RouterObject var router: ConnectToServerCoodinator.Router?
|
||||
@Published var discoveredServers: Set<ServerDiscovery.ServerLookupResponse> = []
|
||||
@Published var searching = false
|
||||
@Published var addServerURIPayload: AddServerURIPayload?
|
||||
@RouterObject
|
||||
var router: ConnectToServerCoodinator.Router?
|
||||
@Published
|
||||
var discoveredServers: Set<ServerDiscovery.ServerLookupResponse> = []
|
||||
@Published
|
||||
var searching = false
|
||||
@Published
|
||||
var addServerURIPayload: AddServerURIPayload?
|
||||
var backAddServerURIPayload: AddServerURIPayload?
|
||||
|
||||
private let discovery = ServerDiscovery()
|
||||
|
@ -58,20 +61,22 @@ final class ConnectToServerViewModel: ViewModel {
|
|||
// This is disgusting. ViewModel Error handling overall needs to be refactored
|
||||
switch completion {
|
||||
case .finished: ()
|
||||
case .failure(let error):
|
||||
case let .failure(error):
|
||||
switch error {
|
||||
case is SwiftfinStore.Errors:
|
||||
let swiftfinError = error as! SwiftfinStore.Errors
|
||||
switch swiftfinError {
|
||||
case .existingServer(let server):
|
||||
case let .existingServer(server):
|
||||
self.addServerURIPayload = AddServerURIPayload(server: server, uri: uri)
|
||||
self.backAddServerURIPayload = AddServerURIPayload(server: server, uri: uri)
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +111,8 @@ final class ConnectToServerViewModel: ViewModel {
|
|||
} receiveValue: { server in
|
||||
SessionManager.main.setServerCurrentURI(server: server, uri: addServerURIPayload.uri)
|
||||
.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)
|
||||
} receiveValue: { _ in
|
||||
self.router?.dismissCoordinator()
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 JellyfinAPI
|
||||
import SwiftUI
|
||||
|
@ -14,9 +13,12 @@ final class EpisodesRowViewModel: ViewModel {
|
|||
|
||||
// TODO: Protocol these viewmodels for generalization instead of Episode
|
||||
|
||||
@ObservedObject var episodeItemViewModel: EpisodeItemViewModel
|
||||
@Published var seasonsEpisodes: [BaseItemDto: [BaseItemDto]] = [:]
|
||||
@Published var selectedSeason: BaseItemDto? {
|
||||
@ObservedObject
|
||||
var episodeItemViewModel: EpisodeItemViewModel
|
||||
@Published
|
||||
var seasonsEpisodes: [BaseItemDto: [BaseItemDto]] = [:]
|
||||
@Published
|
||||
var selectedSeason: BaseItemDto? {
|
||||
willSet {
|
||||
if seasonsEpisodes[newValue!]!.isEmpty {
|
||||
retrieveEpisodesForSeason(newValue!)
|
||||
|
@ -70,8 +72,10 @@ final class SingleSeasonEpisodesRowViewModel: ViewModel {
|
|||
|
||||
// TODO: Protocol these viewmodels for generalization instead of Season
|
||||
|
||||
@ObservedObject var seasonItemViewModel: SeasonItemViewModel
|
||||
@Published var episodes: [BaseItemDto]
|
||||
@ObservedObject
|
||||
var seasonItemViewModel: SeasonItemViewModel
|
||||
@Published
|
||||
var episodes: [BaseItemDto]
|
||||
|
||||
init(seasonItemViewModel: SeasonItemViewModel) {
|
||||
self.seasonItemViewModel = seasonItemViewModel
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 ActivityIndicator
|
||||
import Combine
|
||||
|
@ -14,14 +13,19 @@ import JellyfinAPI
|
|||
|
||||
final class HomeViewModel: ViewModel {
|
||||
|
||||
@Published var latestAddedItems: [BaseItemDto] = []
|
||||
@Published var resumeItems: [BaseItemDto] = []
|
||||
@Published var nextUpItems: [BaseItemDto] = []
|
||||
@Published var librariesShowRecentlyAddedIDs: [String] = []
|
||||
@Published var libraries: [BaseItemDto] = []
|
||||
@Published
|
||||
var latestAddedItems: [BaseItemDto] = []
|
||||
@Published
|
||||
var resumeItems: [BaseItemDto] = []
|
||||
@Published
|
||||
var nextUpItems: [BaseItemDto] = []
|
||||
@Published
|
||||
var librariesShowRecentlyAddedIDs: [String] = []
|
||||
@Published
|
||||
var libraries: [BaseItemDto] = []
|
||||
|
||||
// temp
|
||||
var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded])
|
||||
var recentFilterSet = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded])
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
@ -35,7 +39,8 @@ final class HomeViewModel: ViewModel {
|
|||
nc.addObserver(self, selector: #selector(didSignOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil)
|
||||
}
|
||||
|
||||
@objc private func didSignIn() {
|
||||
@objc
|
||||
private func didSignIn() {
|
||||
for cancellable in cancellables {
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
@ -48,7 +53,8 @@ final class HomeViewModel: ViewModel {
|
|||
refresh()
|
||||
}
|
||||
|
||||
@objc private func didSignOut() {
|
||||
@objc
|
||||
private func didSignOut() {
|
||||
for cancellable in cancellables {
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
@ -56,7 +62,8 @@ final class HomeViewModel: ViewModel {
|
|||
cancellables.removeAll()
|
||||
}
|
||||
|
||||
@objc func refresh() {
|
||||
@objc
|
||||
func refresh() {
|
||||
LogManager.shared.log.debug("Refresh called.")
|
||||
|
||||
refreshLibrariesLatest()
|
||||
|
@ -66,6 +73,7 @@ final class HomeViewModel: ViewModel {
|
|||
}
|
||||
|
||||
// MARK: Libraries Latest Items
|
||||
|
||||
private func refreshLibrariesLatest() {
|
||||
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
||||
.trackActivity(loading)
|
||||
|
@ -82,7 +90,8 @@ final class HomeViewModel: ViewModel {
|
|||
var newLibraries: [BaseItemDto] = []
|
||||
|
||||
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" {
|
||||
newLibraries.append(item)
|
||||
}
|
||||
|
@ -98,11 +107,12 @@ final class HomeViewModel: ViewModel {
|
|||
self.handleAPIRequestError(completion: completion)
|
||||
}
|
||||
}, 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 {
|
||||
newLibraries.removeAll { library in
|
||||
return library.id == excludeID
|
||||
library.id == excludeID
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,9 +124,18 @@ final class HomeViewModel: ViewModel {
|
|||
}
|
||||
|
||||
// MARK: Latest Added Items
|
||||
|
||||
private func refreshLatestAddedItems() {
|
||||
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],
|
||||
enableUserData: true,
|
||||
limit: 8)
|
||||
|
@ -136,10 +155,19 @@ final class HomeViewModel: ViewModel {
|
|||
}
|
||||
|
||||
// MARK: Resume Items
|
||||
|
||||
private func refreshResumeItems() {
|
||||
ItemsAPI.getResumeItems(userId: SessionManager.main.currentLogin.user.id,
|
||||
limit: 6,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
||||
fields: [
|
||||
.primaryImageAspectRatio,
|
||||
.seriesPrimaryImage,
|
||||
.seasonUserData,
|
||||
.overview,
|
||||
.genres,
|
||||
.people,
|
||||
.chapters,
|
||||
],
|
||||
enableUserData: true)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { completion in
|
||||
|
@ -158,10 +186,19 @@ final class HomeViewModel: ViewModel {
|
|||
}
|
||||
|
||||
// MARK: Next Up Items
|
||||
|
||||
private func refreshNextUpItems() {
|
||||
TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id,
|
||||
limit: 6,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
||||
fields: [
|
||||
.primaryImageAspectRatio,
|
||||
.seriesPrimaryImage,
|
||||
.seasonUserData,
|
||||
.overview,
|
||||
.genres,
|
||||
.people,
|
||||
.chapters,
|
||||
],
|
||||
enableUserData: true)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { completion in
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -13,7 +12,8 @@ import JellyfinAPI
|
|||
|
||||
final class CollectionItemViewModel: ItemViewModel {
|
||||
|
||||
@Published var collectionItems: [BaseItemDto] = []
|
||||
@Published
|
||||
var collectionItems: [BaseItemDto] = []
|
||||
|
||||
override init(item: BaseItemDto) {
|
||||
super.init(item: item)
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -14,8 +13,10 @@ import Stinsen
|
|||
|
||||
final class EpisodeItemViewModel: ItemViewModel {
|
||||
|
||||
@RouterObject var itemRouter: ItemCoordinator.Router?
|
||||
@Published var series: BaseItemDto?
|
||||
@RouterObject
|
||||
var itemRouter: ItemCoordinator.Router?
|
||||
@Published
|
||||
var series: BaseItemDto?
|
||||
|
||||
override init(item: BaseItemDto) {
|
||||
super.init(item: item)
|
||||
|
@ -29,7 +30,7 @@ final class EpisodeItemViewModel: ItemViewModel {
|
|||
}
|
||||
|
||||
override func shouldDisplayRuntime() -> Bool {
|
||||
return false
|
||||
false
|
||||
}
|
||||
|
||||
func getEpisodeSeries() {
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -14,19 +13,27 @@ import UIKit
|
|||
|
||||
class ItemViewModel: ViewModel {
|
||||
|
||||
@Published var item: BaseItemDto
|
||||
@Published var playButtonItem: BaseItemDto? {
|
||||
@Published
|
||||
var item: BaseItemDto
|
||||
@Published
|
||||
var playButtonItem: BaseItemDto? {
|
||||
didSet {
|
||||
if let playButtonItem = playButtonItem {
|
||||
refreshItemVideoPlayerViewModel(for: playButtonItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@Published var similarItems: [BaseItemDto] = []
|
||||
@Published var isWatched = false
|
||||
@Published var isFavorited = false
|
||||
@Published var informationItems: [BaseItemDto.ItemDetail]
|
||||
@Published var mediaItems: [BaseItemDto.ItemDetail]
|
||||
|
||||
@Published
|
||||
var similarItems: [BaseItemDto] = []
|
||||
@Published
|
||||
var isWatched = false
|
||||
@Published
|
||||
var isFavorited = false
|
||||
@Published
|
||||
var informationItems: [BaseItemDto.ItemDetail]
|
||||
@Published
|
||||
var mediaItems: [BaseItemDto.ItemDetail]
|
||||
var itemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||
|
||||
init(item: BaseItemDto) {
|
||||
|
@ -70,15 +77,16 @@ class ItemViewModel: ViewModel {
|
|||
}
|
||||
|
||||
func getItemDisplayName() -> String {
|
||||
return item.name ?? ""
|
||||
item.name ?? ""
|
||||
}
|
||||
|
||||
func shouldDisplayRuntime() -> Bool {
|
||||
return true
|
||||
true
|
||||
}
|
||||
|
||||
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)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
import JellyfinAPI
|
||||
|
||||
final class MovieItemViewModel: ItemViewModel {
|
||||
}
|
||||
final class MovieItemViewModel: ItemViewModel {}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -14,9 +13,12 @@ import Stinsen
|
|||
|
||||
final class SeasonItemViewModel: ItemViewModel {
|
||||
|
||||
@RouterObject var itemRouter: ItemCoordinator.Router?
|
||||
@Published var episodes: [BaseItemDto] = []
|
||||
@Published var seriesItem: BaseItemDto?
|
||||
@RouterObject
|
||||
var itemRouter: ItemCoordinator.Router?
|
||||
@Published
|
||||
var episodes: [BaseItemDto] = []
|
||||
@Published
|
||||
var seriesItem: BaseItemDto?
|
||||
|
||||
override init(item: BaseItemDto) {
|
||||
super.init(item: item)
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -13,7 +12,8 @@ import JellyfinAPI
|
|||
|
||||
final class SeriesItemViewModel: ItemViewModel {
|
||||
|
||||
@Published var seasons: [BaseItemDto] = []
|
||||
@Published
|
||||
var seasons: [BaseItemDto] = []
|
||||
|
||||
override init(item: BaseItemDto) {
|
||||
super.init(item: item)
|
||||
|
@ -29,13 +29,15 @@ final class SeriesItemViewModel: ItemViewModel {
|
|||
}
|
||||
|
||||
override func shouldDisplayRuntime() -> Bool {
|
||||
return false
|
||||
false
|
||||
}
|
||||
|
||||
private func getNextUp() {
|
||||
|
||||
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)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
|
@ -67,7 +69,9 @@ final class SeriesItemViewModel: ItemViewModel {
|
|||
|
||||
private func requestSeasons() {
|
||||
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)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -13,7 +12,8 @@ import JellyfinAPI
|
|||
|
||||
final class LatestMediaViewModel: ViewModel {
|
||||
|
||||
@Published var items = [BaseItemDto]()
|
||||
@Published
|
||||
var items = [BaseItemDto]()
|
||||
|
||||
let library: BaseItemDto
|
||||
|
||||
|
@ -34,7 +34,7 @@ final class LatestMediaViewModel: ViewModel {
|
|||
.seasonUserData,
|
||||
.overview,
|
||||
.genres,
|
||||
.people
|
||||
.people,
|
||||
],
|
||||
includeItemTypes: ["Series", "Movie"],
|
||||
enableUserData: true, limit: 12)
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -21,16 +20,25 @@ enum FilterType {
|
|||
|
||||
final class LibraryFilterViewModel: ViewModel {
|
||||
|
||||
@Published var modifiedFilters = LibraryFilters()
|
||||
@Published
|
||||
var modifiedFilters = LibraryFilters()
|
||||
|
||||
@Published var possibleGenres = [NameGuidPair]()
|
||||
@Published var possibleTags = [String]()
|
||||
@Published var possibleSortOrders = APISortOrder.allCases
|
||||
@Published var possibleSortBys = SortBy.allCases
|
||||
@Published var possibleItemFilters = ItemFilter.supportedTypes
|
||||
@Published var enabledFilterType: [FilterType]
|
||||
@Published var selectedSortOrder: APISortOrder = .descending
|
||||
@Published var selectedSortBy: SortBy = .name
|
||||
@Published
|
||||
var possibleGenres = [NameGuidPair]()
|
||||
@Published
|
||||
var possibleTags = [String]()
|
||||
@Published
|
||||
var possibleSortOrders = APISortOrder.allCases
|
||||
@Published
|
||||
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 = ""
|
||||
|
||||
|
@ -44,7 +52,8 @@ final class LibraryFilterViewModel: ViewModel {
|
|||
}
|
||||
|
||||
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.selectedSortBy = filters?.sortBy.first ?? .name
|
||||
self.selectedSortOrder = filters?.sortOrder.first ?? .descending
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
||||
final class LibraryListViewModel: ViewModel {
|
||||
|
||||
@Published var libraries: [BaseItemDto] = []
|
||||
@Published
|
||||
var libraries: [BaseItemDto] = []
|
||||
|
||||
// temp
|
||||
var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: [])
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import CombineExt
|
||||
|
@ -15,15 +14,21 @@ import SwiftUI
|
|||
|
||||
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 var showItems = [BaseItemDto]()
|
||||
@Published var episodeItems = [BaseItemDto]()
|
||||
@Published
|
||||
var movieItems = [BaseItemDto]()
|
||||
@Published
|
||||
var showItems = [BaseItemDto]()
|
||||
@Published
|
||||
var episodeItems = [BaseItemDto]()
|
||||
|
||||
@Published var suggestions = [BaseItemDto]()
|
||||
@Published
|
||||
var suggestions = [BaseItemDto]()
|
||||
|
||||
var searchQuerySubject = CurrentValueSubject<String, Never>("")
|
||||
var parentID: String?
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -26,16 +25,23 @@ final class LibraryViewModel: ViewModel {
|
|||
var genre: NameGuidPair?
|
||||
var studio: NameGuidPair?
|
||||
|
||||
@Published var items = [BaseItemDto]()
|
||||
@Published var rows = [LibraryRow]()
|
||||
@Published
|
||||
var items = [BaseItemDto]()
|
||||
@Published
|
||||
var rows = [LibraryRow]()
|
||||
|
||||
@Published var totalPages = 0
|
||||
@Published var currentPage = 0
|
||||
@Published var hasNextPage = false
|
||||
@Published var hasPreviousPage = false
|
||||
@Published
|
||||
var totalPages = 0
|
||||
@Published
|
||||
var currentPage = 0
|
||||
@Published
|
||||
var hasNextPage = false
|
||||
@Published
|
||||
var hasPreviousPage = false
|
||||
|
||||
// temp
|
||||
@Published var filters: LibraryFilters
|
||||
@Published
|
||||
var filters: LibraryFilters
|
||||
|
||||
private let columns: Int
|
||||
private var libraries = [BaseItemDto]()
|
||||
|
@ -86,8 +92,17 @@ final class LibraryViewModel: ViewModel {
|
|||
searchTerm: nil,
|
||||
sortOrder: filters.sortOrder,
|
||||
parentId: parentID,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
||||
includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series", "BoxSet"],
|
||||
fields: [
|
||||
.primaryImageAspectRatio,
|
||||
.seriesPrimaryImage,
|
||||
.seasonUserData,
|
||||
.overview,
|
||||
.genres,
|
||||
.people,
|
||||
.chapters,
|
||||
],
|
||||
includeItemTypes: filters.filters
|
||||
.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series", "BoxSet"],
|
||||
filters: filters.filters,
|
||||
sortBy: sortBy,
|
||||
tags: filters.tags,
|
||||
|
@ -129,8 +144,17 @@ final class LibraryViewModel: ViewModel {
|
|||
searchTerm: nil,
|
||||
sortOrder: filters.sortOrder,
|
||||
parentId: parentID,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
||||
includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"],
|
||||
fields: [
|
||||
.primaryImageAspectRatio,
|
||||
.seriesPrimaryImage,
|
||||
.seasonUserData,
|
||||
.overview,
|
||||
.genres,
|
||||
.people,
|
||||
.chapters,
|
||||
],
|
||||
includeItemTypes: filters.filters
|
||||
.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"],
|
||||
filters: filters.filters,
|
||||
sortBy: sortBy,
|
||||
tags: filters.tags,
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
@ -26,8 +25,10 @@ struct LiveTVChannelProgram: Hashable {
|
|||
|
||||
final class LiveTVChannelsViewModel: ViewModel {
|
||||
|
||||
@Published var channels = [BaseItemDto]()
|
||||
@Published var channelPrograms = [LiveTVChannelProgram]() {
|
||||
@Published
|
||||
var channels = [BaseItemDto]()
|
||||
@Published
|
||||
var channelPrograms = [LiveTVChannelProgram]() {
|
||||
didSet {
|
||||
rows = []
|
||||
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 channelProgramsList = [BaseItemDto: [BaseItemDto]]()
|
||||
|
@ -64,7 +67,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
|||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
}, receiveValue: { [weak self] response in
|
||||
}, receiveValue: { [weak self] _ in
|
||||
LogManager.shared.log.debug("Received Guide Info")
|
||||
guard let self = self else { return }
|
||||
self.getChannels()
|
||||
|
@ -73,14 +76,12 @@ final class LiveTVChannelsViewModel: ViewModel {
|
|||
}
|
||||
|
||||
func getChannels() {
|
||||
LiveTvAPI.getLiveTvChannels(
|
||||
userId: SessionManager.main.currentLogin.user.id,
|
||||
LiveTvAPI.getLiveTvChannels(userId: SessionManager.main.currentLogin.user.id,
|
||||
startIndex: 0,
|
||||
limit: 1000,
|
||||
enableImageTypes: [.primary],
|
||||
enableUserData: false,
|
||||
enableFavoriteSorting: true
|
||||
)
|
||||
enableFavoriteSorting: true)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
|
@ -95,17 +96,16 @@ final class LiveTVChannelsViewModel: ViewModel {
|
|||
|
||||
private func getPrograms() {
|
||||
// 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. ")
|
||||
return
|
||||
}
|
||||
let channelIds = channels.compactMap { $0.id }
|
||||
let channelIds = channels.compactMap(\.id)
|
||||
|
||||
let minEndDate = Date.now.addComponentsToDate(hours: -1)
|
||||
let maxStartDate = minEndDate.addComponentsToDate(hours: 6)
|
||||
|
||||
let getProgramsDto = GetProgramsDto(
|
||||
channelIds: channelIds,
|
||||
let getProgramsDto = GetProgramsDto(channelIds: channelIds,
|
||||
userId: SessionManager.main.currentLogin.user.id,
|
||||
maxStartDate: maxStartDate,
|
||||
minEndDate: minEndDate,
|
||||
|
@ -114,8 +114,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
|||
enableTotalRecordCount: false,
|
||||
imageTypeLimit: 1,
|
||||
enableImageTypes: [.primary],
|
||||
enableUserData: false
|
||||
)
|
||||
enableUserData: false)
|
||||
|
||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||
.trackActivity(loading)
|
||||
|
@ -146,7 +145,8 @@ final class LiveTVChannelsViewModel: ViewModel {
|
|||
if let startDate = prg.startDate,
|
||||
let endDate = prg.endDate,
|
||||
now.timeIntervalSinceReferenceDate > startDate.timeIntervalSinceReferenceDate &&
|
||||
now.timeIntervalSinceReferenceDate < endDate.timeIntervalSinceReferenceDate {
|
||||
now.timeIntervalSinceReferenceDate < endDate.timeIntervalSinceReferenceDate
|
||||
{
|
||||
currentPrg = prg
|
||||
}
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
|||
if let existingTimer = timer {
|
||||
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 }
|
||||
LogManager.shared.log.debug("LiveTVChannels schedule check...")
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
|
@ -193,7 +193,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
|||
|
||||
extension Array {
|
||||
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)])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
||||
final class LiveTVProgramsViewModel: ViewModel {
|
||||
|
||||
@Published var recommendedItems = [BaseItemDto]()
|
||||
@Published var seriesItems = [BaseItemDto]()
|
||||
@Published var movieItems = [BaseItemDto]()
|
||||
@Published var sportsItems = [BaseItemDto]()
|
||||
@Published var kidsItems = [BaseItemDto]()
|
||||
@Published var newsItems = [BaseItemDto]()
|
||||
@Published
|
||||
var recommendedItems = [BaseItemDto]()
|
||||
@Published
|
||||
var seriesItems = [BaseItemDto]()
|
||||
@Published
|
||||
var movieItems = [BaseItemDto]()
|
||||
@Published
|
||||
var sportsItems = [BaseItemDto]()
|
||||
@Published
|
||||
var kidsItems = [BaseItemDto]()
|
||||
@Published
|
||||
var newsItems = [BaseItemDto]()
|
||||
|
||||
private var channels = [String: BaseItemDto]()
|
||||
|
||||
|
@ -28,18 +33,16 @@ final class LiveTVProgramsViewModel: ViewModel {
|
|||
}
|
||||
|
||||
func findChannel(id: String) -> BaseItemDto? {
|
||||
return channels[id]
|
||||
channels[id]
|
||||
}
|
||||
|
||||
private func getChannels() {
|
||||
LiveTvAPI.getLiveTvChannels(
|
||||
userId: SessionManager.main.currentLogin.user.id,
|
||||
LiveTvAPI.getLiveTvChannels(userId: SessionManager.main.currentLogin.user.id,
|
||||
startIndex: 0,
|
||||
limit: 1000,
|
||||
enableImageTypes: [.primary],
|
||||
enableUserData: false,
|
||||
enableFavoriteSorting: true
|
||||
)
|
||||
enableFavoriteSorting: true)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
|
@ -64,15 +67,13 @@ final class LiveTVProgramsViewModel: ViewModel {
|
|||
}
|
||||
|
||||
private func getRecommendedPrograms() {
|
||||
LiveTvAPI.getRecommendedPrograms(
|
||||
userId: SessionManager.main.currentLogin.user.id,
|
||||
LiveTvAPI.getRecommendedPrograms(userId: SessionManager.main.currentLogin.user.id,
|
||||
limit: 9,
|
||||
isAiring: true,
|
||||
imageTypeLimit: 1,
|
||||
enableImageTypes: [.primary, .thumb],
|
||||
fields: [.channelInfo, .primaryImageAspectRatio],
|
||||
enableTotalRecordCount: false
|
||||
)
|
||||
enableTotalRecordCount: false)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestError(completion: completion)
|
||||
|
@ -95,8 +96,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
|||
limit: 9,
|
||||
enableTotalRecordCount: false,
|
||||
enableImageTypes: [.primary, .thumb],
|
||||
fields: [.channelInfo, .primaryImageAspectRatio]
|
||||
)
|
||||
fields: [.channelInfo, .primaryImageAspectRatio])
|
||||
|
||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||
.trackActivity(loading)
|
||||
|
@ -121,8 +121,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
|||
limit: 9,
|
||||
enableTotalRecordCount: false,
|
||||
enableImageTypes: [.primary, .thumb],
|
||||
fields: [.channelInfo, .primaryImageAspectRatio]
|
||||
)
|
||||
fields: [.channelInfo, .primaryImageAspectRatio])
|
||||
|
||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||
.trackActivity(loading)
|
||||
|
@ -143,8 +142,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
|||
limit: 9,
|
||||
enableTotalRecordCount: false,
|
||||
enableImageTypes: [.primary, .thumb],
|
||||
fields: [.channelInfo, .primaryImageAspectRatio]
|
||||
)
|
||||
fields: [.channelInfo, .primaryImageAspectRatio])
|
||||
|
||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||
.trackActivity(loading)
|
||||
|
@ -165,8 +163,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
|||
limit: 9,
|
||||
enableTotalRecordCount: false,
|
||||
enableImageTypes: [.primary, .thumb],
|
||||
fields: [.channelInfo, .primaryImageAspectRatio]
|
||||
)
|
||||
fields: [.channelInfo, .primaryImageAspectRatio])
|
||||
|
||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||
.trackActivity(loading)
|
||||
|
@ -187,8 +184,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
|||
limit: 9,
|
||||
enableTotalRecordCount: false,
|
||||
enableImageTypes: [.primary, .thumb],
|
||||
fields: [.channelInfo, .primaryImageAspectRatio]
|
||||
)
|
||||
fields: [.channelInfo, .primaryImageAspectRatio])
|
||||
|
||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||
.trackActivity(loading)
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
||||
final class MainTabViewModel: ViewModel {
|
||||
@Published var backgroundURL: URL?
|
||||
@Published var lastBackgroundURL: URL?
|
||||
@Published var backgroundBlurHash: String = "001fC^"
|
||||
@Published
|
||||
var backgroundURL: URL?
|
||||
@Published
|
||||
var lastBackgroundURL: URL?
|
||||
@Published
|
||||
var backgroundBlurHash: String = "001fC^"
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
@ -22,7 +24,8 @@ final class MainTabViewModel: ViewModel {
|
|||
nc.addObserver(self, selector: #selector(backgroundDidChange), name: Notification.Name("backgroundDidChange"), object: nil)
|
||||
}
|
||||
|
||||
@objc func backgroundDidChange() {
|
||||
@objc
|
||||
func backgroundDidChange() {
|
||||
self.lastBackgroundURL = self.backgroundURL
|
||||
self.backgroundURL = BackgroundManager.current.backgroundURL
|
||||
self.backgroundBlurHash = BackgroundManager.current.blurhash
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -15,11 +14,16 @@ import SwiftUICollection
|
|||
|
||||
final class MovieLibrariesViewModel: ViewModel {
|
||||
|
||||
@Published var rows = [LibraryRow]()
|
||||
@Published var totalPages = 0
|
||||
@Published var currentPage = 0
|
||||
@Published var hasNextPage = false
|
||||
@Published var hasPreviousPage = false
|
||||
@Published
|
||||
var rows = [LibraryRow]()
|
||||
@Published
|
||||
var totalPages = 0
|
||||
@Published
|
||||
var currentPage = 0
|
||||
@Published
|
||||
var hasNextPage = false
|
||||
@Published
|
||||
var hasPreviousPage = false
|
||||
|
||||
private var libraries = [BaseItemDto]()
|
||||
private let columns: Int
|
||||
|
@ -27,9 +31,7 @@ final class MovieLibrariesViewModel: ViewModel {
|
|||
@RouterObject
|
||||
var router: MovieLibrariesCoordinator.Router?
|
||||
|
||||
init(
|
||||
columns: Int = 7
|
||||
) {
|
||||
init(columns: Int = 7) {
|
||||
self.columns = columns
|
||||
super.init()
|
||||
|
||||
|
@ -38,8 +40,7 @@ final class MovieLibrariesViewModel: ViewModel {
|
|||
|
||||
func requestLibraries() {
|
||||
|
||||
UserViewsAPI.getUserViews(
|
||||
userId: SessionManager.main.currentLogin.user.id)
|
||||
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { completion in
|
||||
self.handleAPIRequestError(completion: completion)
|
||||
|
@ -62,10 +63,10 @@ final class MovieLibrariesViewModel: ViewModel {
|
|||
}
|
||||
|
||||
private func calculateRows() -> [LibraryRow] {
|
||||
guard libraries.count > 0 else { return [] }
|
||||
guard !libraries.isEmpty else { return [] }
|
||||
let rowCount = libraries.count / columns
|
||||
var calculatedRows = [LibraryRow]()
|
||||
for i in (0...rowCount) {
|
||||
for i in 0 ... rowCount {
|
||||
let firstItemIndex = i * columns
|
||||
var lastItemIndex = firstItemIndex + columns
|
||||
if lastItemIndex > libraries.count {
|
||||
|
@ -83,12 +84,8 @@ final class MovieLibrariesViewModel: ViewModel {
|
|||
rowCells.append(loadingCell)
|
||||
}
|
||||
|
||||
calculatedRows.append(
|
||||
LibraryRow(
|
||||
section: i,
|
||||
items: rowCells
|
||||
)
|
||||
)
|
||||
calculatedRows.append(LibraryRow(section: i,
|
||||
items: rowCells))
|
||||
}
|
||||
return calculatedRows
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import JellyfinAPI
|
||||
|
||||
class ServerDetailViewModel: ViewModel {
|
||||
|
||||
@Published var server: SwiftfinStore.State.Server
|
||||
@Published
|
||||
var server: SwiftfinStore.State.Server
|
||||
|
||||
init(server: SwiftfinStore.State.Server) {
|
||||
self.server = server
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import SwiftUI
|
||||
|
||||
class ServerListViewModel: ObservableObject {
|
||||
|
||||
@Published var servers: [SwiftfinStore.State.Server] = []
|
||||
@Published
|
||||
var servers: [SwiftfinStore.State.Server] = []
|
||||
|
||||
init() {
|
||||
|
||||
|
@ -41,7 +41,8 @@ class ServerListViewModel: ObservableObject {
|
|||
fetchServers()
|
||||
}
|
||||
|
||||
@objc private func didPurge() {
|
||||
@objc
|
||||
private func didPurge() {
|
||||
fetchServers()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Defaults
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
|
||||
final class SettingsViewModel: ObservableObject {
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Foundation
|
||||
|
@ -15,11 +14,16 @@ import SwiftUICollection
|
|||
|
||||
final class TVLibrariesViewModel: ViewModel {
|
||||
|
||||
@Published var rows = [LibraryRow]()
|
||||
@Published var totalPages = 0
|
||||
@Published var currentPage = 0
|
||||
@Published var hasNextPage = false
|
||||
@Published var hasPreviousPage = false
|
||||
@Published
|
||||
var rows = [LibraryRow]()
|
||||
@Published
|
||||
var totalPages = 0
|
||||
@Published
|
||||
var currentPage = 0
|
||||
@Published
|
||||
var hasNextPage = false
|
||||
@Published
|
||||
var hasPreviousPage = false
|
||||
|
||||
private var libraries = [BaseItemDto]()
|
||||
private let columns: Int
|
||||
|
@ -27,9 +31,7 @@ final class TVLibrariesViewModel: ViewModel {
|
|||
@RouterObject
|
||||
var router: TVLibrariesCoordinator.Router?
|
||||
|
||||
init(
|
||||
columns: Int = 7
|
||||
) {
|
||||
init(columns: Int = 7) {
|
||||
self.columns = columns
|
||||
super.init()
|
||||
|
||||
|
@ -38,8 +40,7 @@ final class TVLibrariesViewModel: ViewModel {
|
|||
|
||||
func requestLibraries() {
|
||||
|
||||
UserViewsAPI.getUserViews(
|
||||
userId: SessionManager.main.currentLogin.user.id)
|
||||
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { completion in
|
||||
self.handleAPIRequestError(completion: completion)
|
||||
|
@ -62,10 +63,10 @@ final class TVLibrariesViewModel: ViewModel {
|
|||
}
|
||||
|
||||
private func calculateRows() -> [LibraryRow] {
|
||||
guard libraries.count > 0 else { return [] }
|
||||
guard !libraries.isEmpty else { return [] }
|
||||
let rowCount = libraries.count / columns
|
||||
var calculatedRows = [LibraryRow]()
|
||||
for i in (0...rowCount) {
|
||||
for i in 0 ... rowCount {
|
||||
let firstItemIndex = i * columns
|
||||
var lastItemIndex = firstItemIndex + columns
|
||||
if lastItemIndex > libraries.count {
|
||||
|
@ -83,12 +84,8 @@ final class TVLibrariesViewModel: ViewModel {
|
|||
rowCells.append(loadingCell)
|
||||
}
|
||||
|
||||
calculatedRows.append(
|
||||
LibraryRow(
|
||||
section: i,
|
||||
items: rowCells
|
||||
)
|
||||
)
|
||||
calculatedRows.append(LibraryRow(section: i,
|
||||
items: rowCells))
|
||||
}
|
||||
return calculatedRows
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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
|
||||
import SwiftUI
|
||||
|
||||
class UserListViewModel: ViewModel {
|
||||
|
||||
@Published var users: [SwiftfinStore.State.User] = []
|
||||
@Published
|
||||
var users: [SwiftfinStore.State.User] = []
|
||||
|
||||
var server: SwiftfinStore.State.Server
|
||||
|
||||
|
@ -22,10 +22,12 @@ class UserListViewModel: ViewModel {
|
|||
super.init()
|
||||
|
||||
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") }
|
||||
self.server = newServerState
|
||||
}
|
||||
|
@ -43,5 +45,4 @@ class UserListViewModel: ViewModel {
|
|||
SessionManager.main.delete(user: user)
|
||||
fetchUsers()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 CoreStore
|
||||
import Foundation
|
||||
|
@ -13,7 +12,8 @@ import Stinsen
|
|||
|
||||
final class UserSignInViewModel: ViewModel {
|
||||
|
||||
@RouterObject var router: UserSignInCoordinator.Router?
|
||||
@RouterObject
|
||||
var router: UserSignInCoordinator.Router?
|
||||
let 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",
|
||||
completion: completion)
|
||||
} receiveValue: { _ in
|
||||
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 SwiftUI
|
||||
import JellyfinAPI
|
||||
import SwiftUI
|
||||
|
||||
struct Subtitle {
|
||||
var name: String
|
||||
|
@ -26,6 +25,8 @@ struct AudioTrack {
|
|||
}
|
||||
|
||||
class PlaybackItem: ObservableObject {
|
||||
@Published var videoType: PlayMethod = .directPlay
|
||||
@Published var videoUrl: URL = URL(string: "https://example.com")!
|
||||
@Published
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
// 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
|
||||
|
||||
|
|
|
@ -1,11 +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
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 Combine
|
||||
import Defaults
|
||||
|
@ -25,11 +24,16 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
|
||||
// Manually kept state because VLCKit doesn't properly set "played"
|
||||
// on the VLCMediaPlayer object
|
||||
@Published var playerState: VLCMediaPlayerState = .buffering
|
||||
@Published var leftLabelText: String = "--:--"
|
||||
@Published var rightLabelText: String = "--:--"
|
||||
@Published var playbackSpeed: PlaybackSpeed = .one
|
||||
@Published var subtitlesEnabled: Bool {
|
||||
@Published
|
||||
var playerState: VLCMediaPlayerState = .buffering
|
||||
@Published
|
||||
var leftLabelText: String = "--:--"
|
||||
@Published
|
||||
var rightLabelText: String = "--:--"
|
||||
@Published
|
||||
var playbackSpeed: PlaybackSpeed = .one
|
||||
@Published
|
||||
var subtitlesEnabled: Bool {
|
||||
didSet {
|
||||
if syncSubtitleStateWithAdjacent {
|
||||
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 {
|
||||
if syncSubtitleStateWithAdjacent {
|
||||
previousItemVideoPlayerViewModel?.matchSubtitleStream(with: self)
|
||||
|
@ -46,26 +53,37 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
}
|
||||
}
|
||||
}
|
||||
@Published var previousItemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||
@Published var nextItemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||
@Published var jumpBackwardLength: VideoPlayerJumpLength {
|
||||
|
||||
@Published
|
||||
var previousItemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||
@Published
|
||||
var nextItemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||
@Published
|
||||
var jumpBackwardLength: VideoPlayerJumpLength {
|
||||
willSet {
|
||||
Defaults[.videoPlayerJumpBackward] = newValue
|
||||
}
|
||||
}
|
||||
@Published var jumpForwardLength: VideoPlayerJumpLength {
|
||||
|
||||
@Published
|
||||
var jumpForwardLength: VideoPlayerJumpLength {
|
||||
willSet {
|
||||
Defaults[.videoPlayerJumpForward] = newValue
|
||||
}
|
||||
}
|
||||
@Published var sliderIsScrubbing: Bool = false
|
||||
@Published var sliderPercentage: Double = 0 {
|
||||
|
||||
@Published
|
||||
var sliderIsScrubbing: Bool = false
|
||||
@Published
|
||||
var sliderPercentage: Double = 0 {
|
||||
willSet {
|
||||
sliderScrubbingSubject.send(self)
|
||||
sliderPercentageChanged(newValue: newValue)
|
||||
}
|
||||
}
|
||||
@Published var autoplayEnabled: Bool {
|
||||
|
||||
@Published
|
||||
var autoplayEnabled: Bool {
|
||||
willSet {
|
||||
previousItemVideoPlayerViewModel?.autoplayEnabled = newValue
|
||||
nextItemVideoPlayerViewModel?.autoplayEnabled = newValue
|
||||
|
@ -81,6 +99,7 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
let shouldShowJumpButtonsInOverlayMenu: Bool
|
||||
|
||||
// MARK: General
|
||||
|
||||
let item: BaseItemDto
|
||||
let title: String
|
||||
let subtitle: String?
|
||||
|
@ -93,9 +112,11 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
let streamType: ServerStreamType
|
||||
|
||||
// MARK: Experimental
|
||||
|
||||
let syncSubtitleStateWithAdjacent: Bool
|
||||
|
||||
// MARK: tvOS
|
||||
|
||||
let confirmClose: Bool
|
||||
|
||||
// Full response kept for convenience
|
||||
|
@ -114,17 +135,17 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
}
|
||||
|
||||
var currentSecondTicks: Int64 {
|
||||
return Int64(currentSeconds) * 10_000_000
|
||||
Int64(currentSeconds) * 10_000_000
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
var currentAudioStream: MediaStream? {
|
||||
return audioStreams.first(where: { $0.index == selectedAudioStreamIndex })
|
||||
audioStreams.first(where: { $0.index == selectedAudioStreamIndex })
|
||||
}
|
||||
|
||||
var currentSubtitleStream: MediaStream? {
|
||||
return subtitleStreams.first(where: { $0.index == selectedSubtitleStreamIndex })
|
||||
subtitleStreams.first(where: { $0.index == selectedSubtitleStreamIndex })
|
||||
}
|
||||
|
||||
// Necessary PassthroughSubject to capture manual scrubbing from sliders
|
||||
|
@ -152,7 +173,8 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
overlayType: OverlayType,
|
||||
shouldShowPlayPreviousItem: Bool,
|
||||
shouldShowPlayNextItem: Bool,
|
||||
shouldShowAutoPlay: Bool) {
|
||||
shouldShowAutoPlay: Bool)
|
||||
{
|
||||
self.item = item
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
|
@ -214,6 +236,7 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
}
|
||||
|
||||
// MARK: Adjacent Items
|
||||
|
||||
extension VideoPlayerViewModel {
|
||||
|
||||
func getAdjacentEpisodes() {
|
||||
|
@ -306,7 +329,8 @@ extension VideoPlayerViewModel {
|
|||
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 matchingSubtitleStreamIndex = matchingSubtitleStream.index else { return }
|
||||
|
||||
|
@ -325,24 +349,27 @@ extension VideoPlayerViewModel {
|
|||
}
|
||||
|
||||
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
|
||||
|
||||
extension VideoPlayerViewModel {
|
||||
|
||||
private func sendNewProgressReportWithTimer() {
|
||||
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
|
||||
|
||||
extension VideoPlayerViewModel {
|
||||
|
||||
|
||||
// MARK: sendPlayReport
|
||||
|
||||
func sendPlayReport() {
|
||||
|
||||
self.startTimeTicks = Int64(Date().timeIntervalSince1970) * 10_000_000
|
||||
|
@ -368,8 +395,7 @@ extension VideoPlayerViewModel {
|
|||
playSessionId: response.playSessionId,
|
||||
repeatMode: .repeatNone,
|
||||
nowPlayingQueue: nil,
|
||||
playlistItemId: "playlistItem0"
|
||||
)
|
||||
playlistItemId: "playlistItem0")
|
||||
|
||||
PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo)
|
||||
.sink { completion in
|
||||
|
@ -381,6 +407,7 @@ extension VideoPlayerViewModel {
|
|||
}
|
||||
|
||||
// MARK: sendPauseReport
|
||||
|
||||
func sendPauseReport(paused: Bool) {
|
||||
|
||||
let subtitleStreamIndex = subtitlesEnabled ? selectedSubtitleStreamIndex : nil
|
||||
|
@ -404,8 +431,7 @@ extension VideoPlayerViewModel {
|
|||
playSessionId: response.playSessionId,
|
||||
repeatMode: .repeatNone,
|
||||
nowPlayingQueue: nil,
|
||||
playlistItemId: "playlistItem0"
|
||||
)
|
||||
playlistItemId: "playlistItem0")
|
||||
|
||||
PlaystateAPI.reportPlaybackStart(playbackStartInfo: pauseInfo)
|
||||
.sink { completion in
|
||||
|
@ -417,6 +443,7 @@ extension VideoPlayerViewModel {
|
|||
}
|
||||
|
||||
// MARK: sendProgressReport
|
||||
|
||||
func sendProgressReport() {
|
||||
|
||||
let subtitleStreamIndex = subtitlesEnabled ? selectedSubtitleStreamIndex : nil
|
||||
|
@ -447,7 +474,8 @@ extension VideoPlayerViewModel {
|
|||
self.sendNewProgressReportWithTimer()
|
||||
}
|
||||
|
||||
@objc private func _sendProgressReport() {
|
||||
@objc
|
||||
private func _sendProgressReport() {
|
||||
guard let lastProgressReport = lastProgressReport else { return }
|
||||
|
||||
PlaystateAPI.reportPlaybackProgress(playbackProgressInfo: lastProgressReport)
|
||||
|
@ -462,6 +490,7 @@ extension VideoPlayerViewModel {
|
|||
}
|
||||
|
||||
// MARK: sendStopReport
|
||||
|
||||
func sendStopReport() {
|
||||
|
||||
let stopInfo = PlaybackStopInfo(item: item,
|
||||
|
@ -487,6 +516,7 @@ extension VideoPlayerViewModel {
|
|||
}
|
||||
|
||||
// MARK: Embedded/Normal Subtitle Streams
|
||||
|
||||
extension VideoPlayerViewModel {
|
||||
|
||||
func createEmbeddedSubtitleStream(with subtitleStream: MediaStream) -> URL {
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
//
|
||||
/*
|
||||
* 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
// 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 ActivityIndicator
|
||||
import Combine
|
||||
import Foundation
|
||||
import ActivityIndicator
|
||||
import JellyfinAPI
|
||||
|
||||
class ViewModel: ObservableObject {
|
||||
|
||||
@Published var isLoading = false
|
||||
@Published var errorMessage: ErrorMessage?
|
||||
@Published
|
||||
var isLoading = false
|
||||
@Published
|
||||
var errorMessage: ErrorMessage?
|
||||
|
||||
let loading = ActivityIndicator()
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
@ -24,12 +25,15 @@ class ViewModel: ObservableObject {
|
|||
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 {
|
||||
case .finished:
|
||||
self.errorMessage = nil
|
||||
case .failure(let error):
|
||||
let logConstructor = LogConstructor(message: "__NOTHING__", tag: tag, level: logLevel, function: function, file: file, line: line)
|
||||
case let .failure(error):
|
||||
let logConstructor = LogConstructor(message: "__NOTHING__", tag: tag, level: logLevel, function: function, file: file,
|
||||
line: line)
|
||||
|
||||
switch error {
|
||||
case is ErrorResponse:
|
||||
|
@ -39,14 +43,17 @@ class ViewModel: ObservableObject {
|
|||
case .error(-1, _, _, _):
|
||||
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
|
||||
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, _, _, _):
|
||||
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:
|
||||
networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
|
||||
// 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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue