swiftformat

This commit is contained in:
Ethan Pippin 2022-01-10 12:28:03 -07:00
parent 961e639970
commit 4298062ca3
223 changed files with 16548 additions and 15983 deletions

View File

@ -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

View File

@ -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())
}
}

View File

@ -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())
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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))
}
}

View File

@ -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

View File

@ -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,16 +14,19 @@ 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> {
// NavigationViewCoordinator(VideoPlayerCoordinator(item: item))
// NavigationViewCoordinator(VideoPlayerCoordinator(item: item))
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 {

View File

@ -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,13 @@ 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))
// NavigationViewCoordinator(VideoPlayerCoordinator(item: item))
NavigationViewCoordinator(EmptyViewCoordinator())
}

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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")
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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())
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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] = {
@ -148,19 +133,19 @@ extension String {
}
private extension String {
subscript (offset: Int) -> Character {
return self[index(startIndex, offsetBy: offset)]
subscript(offset: Int) -> Character {
self[index(startIndex, offsetBy: offset)]
}
subscript (bounds: CountableClosedRange<Int>) -> Substring {
subscript(bounds: CountableClosedRange<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return self[start...end]
return self[start ... end]
}
subscript (bounds: CountableRange<Int>) -> Substring {
subscript(bounds: CountableRange<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return self[start..<end]
return self[start ..< end]
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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))
}
}

View File

@ -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,12 +47,13 @@ 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
if let lastOpenIndex = lastRole.lastIndex(of: "("), let lastClosingIndex = lastRole.lastIndex(of: ")") {
let roleText = lastRole[lastOpenIndex...lastClosingIndex]
let roleText = lastRole[lastOpenIndex ... lastClosingIndex]
final.append(" \(roleText)")
}
@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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 ?? ""
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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()
@ -163,7 +164,7 @@ open class UDPBroadcastConnection {
// debugPrint("UDP connection received \(bytesRead) bytes from \(endpoint.host):\(endpoint.port)")
let responseBytes = Data(response[0..<bytesRead])
let responseBytes = Data(response[0 ..< bytesRead])
// Handle response
self.handler?(endpoint.host, endpoint.port, responseBytes)
@ -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)
}
}

View File

@ -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

View File

@ -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)"
}

View File

@ -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,21 +282,25 @@ 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)
}
private func _delete(user: SwiftfinStore.Models.StoredUser, transaction: UnsafeDataTransaction?) {
guard let storedAccessToken = user.accessToken else { fatalError("No access token for stored user?")}
guard let storedAccessToken = user.accessToken else { fatalError("No access token for stored user?") }
let transaction = transaction == nil ? SwiftfinStore.dataStack.beginUnsafe() : transaction!
transaction.delete(storedAccessToken)

View File

@ -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 {

View File

@ -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"
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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() {

View File

@ -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)

View File

@ -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 {}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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: [])

View File

@ -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?

View File

@ -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,

View File

@ -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)])
}
}

View File

@ -1,25 +1,30 @@
//
/*
* 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]()
private var channels = [String: BaseItemDto]()
override init() {
super.init()
@ -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)

View File

@ -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

View File

@ -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 {
@ -73,7 +74,7 @@ final class MovieLibrariesViewModel: ViewModel {
}
var rowCells = [LibraryRowCell]()
for item in libraries[firstItemIndex..<lastItemIndex] {
for item in libraries[firstItemIndex ..< lastItemIndex] {
let newCell = LibraryRowCell(item: item)
rowCells.append(newCell)
}
@ -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
}

View File

@ -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

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -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 {
@ -73,7 +74,7 @@ final class TVLibrariesViewModel: ViewModel {
}
var rowCells = [LibraryRowCell]()
for item in libraries[firstItemIndex..<lastItemIndex] {
for item in libraries[firstItemIndex ..< lastItemIndex] {
let newCell = LibraryRowCell(item: item)
rowCells.append(newCell)
}
@ -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
}

View File

@ -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()
}
}

View File

@ -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)
}

View File

@ -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")!
}

View File

@ -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

View File

@ -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
@ -14,9 +13,9 @@ import JellyfinAPI
import UIKit
#if os(tvOS)
import TVVLCKit
import TVVLCKit
#else
import MobileVLCKit
import MobileVLCKit
#endif
final class VideoPlayerViewModel: ViewModel {
@ -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 {

View File

@ -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