migrate stinsen v1 to v2
This commit is contained in:
parent
1863d973a9
commit
16e3cd6ea5
|
@ -977,7 +977,6 @@
|
|||
6267B3D92671138200A7371D /* ImageExtensions.swift */,
|
||||
E1AD105226D96D5F003E4A08 /* JellyfinAPIExtensions */,
|
||||
621338922660107500A81A2A /* StringExtensions.swift */,
|
||||
624C21742685CF60007F1390 /* SearchablePickerView.swift */,
|
||||
6220D0AC26D5EABB00B8E046 /* ViewExtensions.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
|
@ -1535,7 +1534,7 @@
|
|||
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
|
||||
E1AD105D26D9ABDD003E4A08 /* PillHStackView.swift in Sources */,
|
||||
E188460526DEF04800B0C5B7 /* CardVStackView.swift in Sources */,
|
||||
5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */,
|
||||
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
|
||||
6220D0BE26D60D6600B8E046 /* ItemViewModel.swift in Sources */,
|
||||
531690FA267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift in Sources */,
|
||||
E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */,
|
||||
|
@ -1551,7 +1550,7 @@
|
|||
files = (
|
||||
5364F455266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
|
||||
E18845F526DD631E00B0C5B7 /* BaseItemDto+Stackable.swift in Sources */,
|
||||
5364F455266CA0DC0026ECBA /* APIExtensions.swift in Sources */,
|
||||
5364F455266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
|
||||
6220D0B426D5ED8000B8E046 /* LibraryCoordinator.swift in Sources */,
|
||||
6220D0C026D61C5000B8E046 /* ItemCoordinator.swift in Sources */,
|
||||
621338932660107500A81A2A /* StringExtensions.swift in Sources */,
|
||||
|
@ -1566,7 +1565,6 @@
|
|||
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
|
||||
53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */,
|
||||
E1AD105626D981CE003E4A08 /* PortraitHStackView.swift in Sources */,
|
||||
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */,
|
||||
62C29EA126D102A500C1D2E7 /* MainTabCoordinator.swift in Sources */,
|
||||
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
|
||||
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,
|
||||
|
@ -1607,7 +1605,7 @@
|
|||
625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */,
|
||||
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */,
|
||||
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
|
||||
62E632F3267D54030063E547 /* DetailItemViewModel.swift in Sources */,
|
||||
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */,
|
||||
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
|
||||
E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */,
|
||||
E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */,
|
||||
|
@ -1636,7 +1634,6 @@
|
|||
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
||||
09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */,
|
||||
53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */,
|
||||
6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */,
|
||||
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
|
||||
E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */,
|
||||
|
@ -2246,7 +2243,7 @@
|
|||
repositoryURL = "https://github.com/rundfunk47/stinsen";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.1.0;
|
||||
minimumVersion = 2.0.2;
|
||||
};
|
||||
};
|
||||
62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */ = {
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
"repositoryURL": "https://github.com/Flight-School/AnyCodable",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "69261f239f0fffaf51495dadc4f8483fbfe97025",
|
||||
"version": "0.6.1"
|
||||
"revision": "b1a7a8a6186f2fcb28f7bda67cfc545de48b3c80",
|
||||
"version": "0.6.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -24,8 +24,8 @@
|
|||
"repositoryURL": "https://github.com/pointfreeco/combine-schedulers",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "6dcc7c034d28fe7ac652453faeae07656f723909",
|
||||
"version": "0.5.1"
|
||||
"revision": "6bde3b0063ba8e7537b43744948535ca7e9e0dad",
|
||||
"version": "0.5.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -78,8 +78,8 @@
|
|||
"repositoryURL": "https://github.com/kean/Nuke.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "3bd3a1765bdf62d561d4c2e10e1c4fc7a010f44e",
|
||||
"version": "10.3.2"
|
||||
"revision": "0db18dd34998cca18e9a28bcee136f84518007a0",
|
||||
"version": "10.4.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -105,8 +105,8 @@
|
|||
"repositoryURL": "https://github.com/sushichop/Puppy",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "d670c669ce2a6ab554a903b815f461d6efc565e4",
|
||||
"version": "0.3.0"
|
||||
"revision": "95ce04b0e778b8d7c351876bc98bbf68328dfc9b",
|
||||
"version": "0.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -114,8 +114,8 @@
|
|||
"repositoryURL": "https://github.com/rundfunk47/stinsen",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "e72c20b2c4bde0d6c3a911d4eda688fee7aa3bba",
|
||||
"version": "1.1.0"
|
||||
"revision": "3d06c7603c70f8af1bd49f8d49f17e98f25b2d6a",
|
||||
"version": "2.0.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -159,8 +159,8 @@
|
|||
"repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "b9eeb1a7ea3fd6fea54ce57dee2f5794b667c8df",
|
||||
"version": "0.2.0"
|
||||
"revision": "50a70a9d3583fe228ce672e8923010c8df2deddd",
|
||||
"version": "0.2.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,91 +1,85 @@
|
|||
//
|
||||
/*
|
||||
* SwiftFin is subject to the terms of the Mozilla Public
|
||||
* License, v2.0. 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import JellyfinAPI
|
||||
import SwiftUI
|
||||
|
||||
struct PortraitItemView: View {
|
||||
|
||||
var item: BaseItemDto
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(destination: LazyView { ItemNavigationView(item: item) }) {
|
||||
VStack(alignment: .leading) {
|
||||
ImageView(src: item.type != "Episode" ? item.getPrimaryImage(maxWidth: 100) : item.getSeriesPrimaryImage(maxWidth: 100), bh: item.type != "Episode" ? item.getPrimaryImageBlurHash() : item.getSeriesPrimaryImageBlurHash())
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: 4, y: 2)
|
||||
.shadow(radius: 4, y: 2)
|
||||
.overlay(
|
||||
Rectangle()
|
||||
.fill(Color(red: 172/255, green: 92/255, blue: 195/255))
|
||||
.mask(ProgressBar())
|
||||
.frame(width: CGFloat(item.userData?.playedPercentage ?? 0), height: 7)
|
||||
.padding(0), alignment: .bottomLeading
|
||||
)
|
||||
.overlay(
|
||||
ZStack {
|
||||
if item.userData?.isFavorite ?? false {
|
||||
Image(systemName: "circle.fill")
|
||||
.foregroundColor(.white)
|
||||
.opacity(0.6)
|
||||
Image(systemName: "heart.fill")
|
||||
.foregroundColor(Color(.systemRed))
|
||||
.font(.system(size: 10))
|
||||
}
|
||||
}
|
||||
.padding(.leading, 2)
|
||||
.padding(.bottom, item.userData?.playedPercentage == nil ? 2 : 9)
|
||||
.opacity(1), alignment: .bottomLeading)
|
||||
.overlay(
|
||||
ZStack {
|
||||
if item.userData?.played ?? false {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.accentColor)
|
||||
.background(Color(.white))
|
||||
.clipShape(Circle().scale(0.8))
|
||||
} else {
|
||||
if item.userData?.unplayedItemCount != nil {
|
||||
Capsule()
|
||||
.fill(Color.accentColor)
|
||||
.frame(minWidth: 20, minHeight: 20, maxHeight: 20)
|
||||
Text(String(item.userData!.unplayedItemCount ?? 0))
|
||||
.foregroundColor(.white)
|
||||
.font(.caption2)
|
||||
.padding(2)
|
||||
}
|
||||
}
|
||||
}.padding(2)
|
||||
.fixedSize()
|
||||
.opacity(1), alignment: .topTrailing).opacity(1)
|
||||
Text(item.seriesName ?? item.name ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
if item.type == "Movie" || item.type == "Series" {
|
||||
Text("\(String(item.productionYear ?? 0)) • \(item.officialRating ?? "N/A")")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
} else if item.type == "Season" {
|
||||
Text("\(item.name ?? "") • \(String(item.productionYear ?? 0))")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
} else {
|
||||
Text("S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0))")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
VStack(alignment: .leading) {
|
||||
ImageView(src: item.type != "Episode" ? item.getPrimaryImage(maxWidth: 100) : item.getSeriesPrimaryImage(maxWidth: 100),
|
||||
bh: item.type != "Episode" ? item.getPrimaryImageBlurHash() : item.getSeriesPrimaryImageBlurHash())
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: 4, y: 2)
|
||||
.shadow(radius: 4, y: 2)
|
||||
.overlay(Rectangle()
|
||||
.fill(Color(red: 172 / 255, green: 92 / 255, blue: 195 / 255))
|
||||
.mask(ProgressBar())
|
||||
.frame(width: CGFloat(item.userData?.playedPercentage ?? 0), height: 7)
|
||||
.padding(0), alignment: .bottomLeading)
|
||||
.overlay(ZStack {
|
||||
if item.userData?.isFavorite ?? false {
|
||||
Image(systemName: "circle.fill")
|
||||
.foregroundColor(.white)
|
||||
.opacity(0.6)
|
||||
Image(systemName: "heart.fill")
|
||||
.foregroundColor(Color(.systemRed))
|
||||
.font(.system(size: 10))
|
||||
}
|
||||
}
|
||||
}.frame(width: 100)
|
||||
}
|
||||
.padding(.leading, 2)
|
||||
.padding(.bottom, item.userData?.playedPercentage == nil ? 2 : 9)
|
||||
.opacity(1), alignment: .bottomLeading)
|
||||
.overlay(ZStack {
|
||||
if item.userData?.played ?? false {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.accentColor)
|
||||
.background(Color(.white))
|
||||
.clipShape(Circle().scale(0.8))
|
||||
} else {
|
||||
if item.userData?.unplayedItemCount != nil {
|
||||
Capsule()
|
||||
.fill(Color.accentColor)
|
||||
.frame(minWidth: 20, minHeight: 20, maxHeight: 20)
|
||||
Text(String(item.userData!.unplayedItemCount ?? 0))
|
||||
.foregroundColor(.white)
|
||||
.font(.caption2)
|
||||
.padding(2)
|
||||
}
|
||||
}
|
||||
}.padding(2)
|
||||
.fixedSize()
|
||||
.opacity(1), alignment: .topTrailing).opacity(1)
|
||||
Text(item.seriesName ?? item.name ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
if item.type == "Movie" || item.type == "Series" {
|
||||
Text("\(String(item.productionYear ?? 0)) • \(item.officialRating ?? "N/A")")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
} else if item.type == "Season" {
|
||||
Text("\(item.name ?? "") • \(String(item.productionYear ?? 0))")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
} else {
|
||||
Text("S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0))")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
}
|
||||
}.frame(width: 100)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import SwiftUI
|
|||
import Stinsen
|
||||
|
||||
struct ConnectToServerView: View {
|
||||
@EnvironmentObject var mainRouter: ViewRouter<MainCoordinator.Route>
|
||||
@EnvironmentObject var mainRouter: MainCoordinator.Router
|
||||
@StateObject var viewModel = ConnectToServerViewModel()
|
||||
@State var username = ""
|
||||
@State var password = ""
|
||||
|
@ -61,7 +61,7 @@ struct ConnectToServerView: View {
|
|||
if SessionManager.current.doesUserHaveSavedSession(userID: publicUser.id!) {
|
||||
let user = SessionManager.current.getSavedSession(userID: publicUser.id!)
|
||||
SessionManager.current.loginWithSavedSession(user: user)
|
||||
mainRouter.route(to: .mainTab)
|
||||
mainRouter.root(\.mainTab)
|
||||
} else {
|
||||
username = publicUser.name ?? ""
|
||||
viewModel.selectedPublicUser = publicUser
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import JellyfinAPI
|
||||
import SwiftUI
|
||||
|
||||
struct ProgressBar: Shape {
|
||||
func path(in rect: CGRect) -> Path {
|
||||
|
@ -31,26 +31,27 @@ struct ProgressBar: Shape {
|
|||
}
|
||||
|
||||
struct ContinueWatchingView: View {
|
||||
@EnvironmentObject var homeRouter: HomeCoordinator.Router
|
||||
var items: [BaseItemDto]
|
||||
|
||||
var body: some View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
LazyHStack {
|
||||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: LazyView { ItemNavigationView(item: item) }) {
|
||||
Button {
|
||||
homeRouter.route(to: \.item, item)
|
||||
} label: {
|
||||
VStack(alignment: .leading) {
|
||||
ImageView(src: item.getBackdropImage(maxWidth: 320), bh: item.getBackdropImageBlurHash())
|
||||
.frame(width: 320, height: 180)
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: 4, y: 2)
|
||||
.shadow(radius: 4, y: 2)
|
||||
.overlay(
|
||||
Rectangle()
|
||||
.fill(Color(red: 172/255, green: 92/255, blue: 195/255))
|
||||
.mask(ProgressBar())
|
||||
.frame(width: CGFloat((item.userData?.playedPercentage ?? 0) * 3.2), height: 7)
|
||||
.padding(0), alignment: .bottomLeading
|
||||
)
|
||||
.overlay(Rectangle()
|
||||
.fill(Color(red: 172 / 255, green: 92 / 255, blue: 195 / 255))
|
||||
.mask(ProgressBar())
|
||||
.frame(width: CGFloat((item.userData?.playedPercentage ?? 0) * 3.2), height: 7)
|
||||
.padding(0), alignment: .bottomLeading)
|
||||
HStack {
|
||||
Text("\(item.seriesName ?? item.name ?? "")")
|
||||
.font(.callout)
|
||||
|
@ -68,11 +69,11 @@ struct ContinueWatchingView: View {
|
|||
Spacer()
|
||||
}.frame(width: 320, alignment: .leading)
|
||||
}.padding(.top, 10)
|
||||
.padding(.bottom, 5)
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
}.padding(.trailing, 16)
|
||||
}.frame(height: 215)
|
||||
.padding(EdgeInsets(top: 8, leading: 20, bottom: 10, trailing: 2))
|
||||
.padding(EdgeInsets(top: 8, leading: 20, bottom: 10, trailing: 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,14 +12,11 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
final class ConnectToServerCoodinator: NavigationCoordinatable {
|
||||
var navigationStack = NavigationStack()
|
||||
let stack = NavigationStack(initial: \ConnectToServerCoodinator.start)
|
||||
|
||||
enum Route: NavigationRoute {}
|
||||
|
||||
func resolveRoute(route: Route) -> Transition {}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
@Root var start = makeStart
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
ConnectToServerView()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,12 @@ import Foundation
|
|||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
typealias FilterCoordinatorParams = (filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String)
|
||||
|
||||
final class FilterCoordinator: NavigationCoordinatable {
|
||||
var navigationStack = NavigationStack()
|
||||
let stack = NavigationStack(initial: \FilterCoordinator.start)
|
||||
@Root var start = makeStart
|
||||
|
||||
@Binding var filters: LibraryFilters
|
||||
var enabledFilterType: [FilterType]
|
||||
var parentId: String = ""
|
||||
|
@ -23,12 +27,7 @@ final class FilterCoordinator: NavigationCoordinatable {
|
|||
self.parentId = parentId
|
||||
}
|
||||
|
||||
enum Route: NavigationRoute {}
|
||||
|
||||
func resolveRoute(route: Route) -> Transition {}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: parentId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,31 +8,31 @@
|
|||
*/
|
||||
|
||||
import Foundation
|
||||
import JellyfinAPI
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
final class HomeCoordinator: NavigationCoordinatable {
|
||||
var navigationStack = NavigationStack()
|
||||
let stack = NavigationStack(initial: \HomeCoordinator.start)
|
||||
|
||||
enum Route: NavigationRoute {
|
||||
case settings
|
||||
case library(viewModel: LibraryViewModel, title: String)
|
||||
case item(viewModel: ItemViewModel)
|
||||
@Root var start = makeStart
|
||||
@Route(.modal) var settings = makeSettings
|
||||
@Route(.push) var library = makeLibrary
|
||||
@Route(.push) var item = makeItem
|
||||
|
||||
func makeSettings() -> NavigationViewCoordinator<SettingsCoordinator> {
|
||||
NavigationViewCoordinator(SettingsCoordinator())
|
||||
}
|
||||
|
||||
func resolveRoute(route: Route) -> Transition {
|
||||
switch route {
|
||||
case .settings:
|
||||
return .modal(NavigationViewCoordinator(SettingsCoordinator()).eraseToAnyCoordinatable())
|
||||
case let .library(viewModel, title):
|
||||
return .push(LibraryCoordinator(viewModel: viewModel, title: title).eraseToAnyCoordinatable())
|
||||
case let .item(viewModel):
|
||||
return .push(ItemCoordinator(viewModel: viewModel).eraseToAnyCoordinatable())
|
||||
}
|
||||
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
|
||||
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||
ItemCoordinator(item: item)
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
HomeView()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,32 +13,32 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
final class ItemCoordinator: NavigationCoordinatable {
|
||||
var navigationStack = NavigationStack()
|
||||
var viewModel: ItemViewModel
|
||||
let stack = NavigationStack(initial: \ItemCoordinator.start)
|
||||
|
||||
init(viewModel: ItemViewModel) {
|
||||
self.viewModel = viewModel
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var item = makeItem
|
||||
@Route(.push) var library = makeLibrary
|
||||
@Route(.fullScreen) var videoPlayer = makeVideoPlayer
|
||||
|
||||
let itemDto: BaseItemDto
|
||||
|
||||
init(item: BaseItemDto) {
|
||||
self.itemDto = item
|
||||
}
|
||||
|
||||
enum Route: NavigationRoute {
|
||||
case item(viewModel: ItemViewModel)
|
||||
case library(viewModel: LibraryViewModel, title: String)
|
||||
case videoPlayer(item: BaseItemDto)
|
||||
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
|
||||
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
|
||||
}
|
||||
|
||||
func resolveRoute(route: Route) -> Transition {
|
||||
switch route {
|
||||
case let .item(viewModel):
|
||||
return .push(ItemCoordinator(viewModel: viewModel).eraseToAnyCoordinatable())
|
||||
case let .library(viewModel, title):
|
||||
return .push(LibraryCoordinator(viewModel: viewModel, title: title).eraseToAnyCoordinatable())
|
||||
case let .videoPlayer(item):
|
||||
return .fullScreen(NavigationViewCoordinator(VideoPlayerCoordinator(item: item)).eraseToAnyCoordinatable())
|
||||
}
|
||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||
ItemCoordinator(item: item)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
ItemView(viewModel: self.viewModel)
|
||||
func makeVideoPlayer(item: BaseItemDto) -> NavigationViewCoordinator<VideoPlayerCoordinator> {
|
||||
NavigationViewCoordinator(VideoPlayerCoordinator(item: item))
|
||||
}
|
||||
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
ItemNavigationView(item: itemDto)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,20 @@
|
|||
*/
|
||||
|
||||
import Foundation
|
||||
import JellyfinAPI
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
typealias LibraryCoordinatorParams = (viewModel: LibraryViewModel, title: String)
|
||||
|
||||
final class LibraryCoordinator: NavigationCoordinatable {
|
||||
var navigationStack = NavigationStack()
|
||||
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
|
||||
|
||||
var viewModel: LibraryViewModel
|
||||
var title: String
|
||||
|
||||
|
@ -21,28 +30,21 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
|||
self.title = title
|
||||
}
|
||||
|
||||
enum Route: NavigationRoute {
|
||||
case search(viewModel: LibrarySearchViewModel)
|
||||
case filter(filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String)
|
||||
case item(viewModel: ItemViewModel)
|
||||
}
|
||||
|
||||
func resolveRoute(route: Route) -> Transition {
|
||||
switch route {
|
||||
case let .search(viewModel):
|
||||
return .push(SearchCoordinator(viewModel: viewModel).eraseToAnyCoordinatable())
|
||||
case let .filter(filters, enabledFilterType, parentId):
|
||||
return .modal(FilterCoordinator(filters: filters,
|
||||
enabledFilterType: enabledFilterType,
|
||||
parentId: parentId)
|
||||
.eraseToAnyCoordinatable())
|
||||
case let .item(viewModel):
|
||||
return .push(ItemCoordinator(viewModel: viewModel).eraseToAnyCoordinatable())
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
LibraryView(viewModel: self.viewModel, title: title)
|
||||
}
|
||||
|
||||
func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator {
|
||||
SearchCoordinator(viewModel: viewModel)
|
||||
}
|
||||
|
||||
func makeFilter(params: FilterCoordinatorParams) -> NavigationViewCoordinator<FilterCoordinator> {
|
||||
NavigationViewCoordinator(FilterCoordinator(filters: params.filters,
|
||||
enabledFilterType: params.enabledFilterType,
|
||||
parentId: params.parentId))
|
||||
}
|
||||
|
||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||
ItemCoordinator(item: item)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,24 +12,22 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
final class LibraryListCoordinator: NavigationCoordinatable {
|
||||
var navigationStack = NavigationStack()
|
||||
let stack = NavigationStack(initial: \LibraryListCoordinator.start)
|
||||
|
||||
enum Route: NavigationRoute {
|
||||
case search(viewModel: LibrarySearchViewModel)
|
||||
case library(viewModel: LibraryViewModel, title: String)
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var search = makeSearch
|
||||
@Route(.push) var library = makeLibrary
|
||||
|
||||
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
|
||||
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
|
||||
}
|
||||
|
||||
func resolveRoute(route: Route) -> Transition {
|
||||
switch route {
|
||||
case let .search(viewModel):
|
||||
return .push(SearchCoordinator(viewModel: viewModel).eraseToAnyCoordinatable())
|
||||
case let .library(viewModel, title):
|
||||
return .push(LibraryCoordinator(viewModel: viewModel, title: title).eraseToAnyCoordinatable())
|
||||
}
|
||||
func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator {
|
||||
SearchCoordinator(viewModel: viewModel)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
func makeStart() -> some View {
|
||||
LibraryListView()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,55 +8,68 @@
|
|||
*/
|
||||
|
||||
import Foundation
|
||||
import Nuke
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
#if !os(tvOS)
|
||||
import WidgetKit
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
final class MainCoordinator: ViewCoordinatable {
|
||||
var children = ViewChild()
|
||||
final class MainCoordinator: NavigationCoordinatable {
|
||||
var stack: NavigationStack<MainCoordinator>
|
||||
|
||||
enum Route: ViewRoute {
|
||||
case mainTab
|
||||
case connectToServer
|
||||
}
|
||||
@Root var mainTab = makeMainTab
|
||||
@Root var connectToServer = makeConnectToServer
|
||||
|
||||
func resolveRoute(route: Route) -> AnyCoordinatable {
|
||||
switch route {
|
||||
case .mainTab:
|
||||
return MainTabCoordinator().eraseToAnyCoordinatable()
|
||||
case .connectToServer:
|
||||
return NavigationViewCoordinator(ConnectToServerCoodinator()).eraseToAnyCoordinatable()
|
||||
init() {
|
||||
if ServerEnvironment.current.server != nil, SessionManager.current.user != nil {
|
||||
self.stack = NavigationStack(initial: \MainCoordinator.mainTab)
|
||||
} else {
|
||||
self.stack = NavigationStack(initial: \MainCoordinator.connectToServer)
|
||||
}
|
||||
ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory
|
||||
DataLoader.sharedUrlCache.diskCapacity = 1000 * 1024 * 1024 // 1000MB disk
|
||||
|
||||
#if !os(tvOS)
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
UIScrollView.appearance().keyboardDismissMode = .onDrag
|
||||
#endif
|
||||
|
||||
let nc = NotificationCenter.default
|
||||
nc.addObserver(self, selector: #selector(didLogIn), name: Notification.Name("didSignIn"), object: nil)
|
||||
nc.addObserver(self, selector: #selector(didLogOut), name: Notification.Name("didSignOut"), object: nil)
|
||||
}
|
||||
|
||||
@objc func didLogIn() {
|
||||
LogManager.shared.log.info("Received `didSignIn` from NSNotificationCenter.")
|
||||
root(\.mainTab)
|
||||
}
|
||||
|
||||
@objc func didLogOut() {
|
||||
LogManager.shared.log.info("Received `didSignOut` from NSNotificationCenter.")
|
||||
root(\.connectToServer)
|
||||
}
|
||||
|
||||
func makeMainTab() -> MainTabCoordinator {
|
||||
MainTabCoordinator()
|
||||
}
|
||||
|
||||
func makeConnectToServer() -> NavigationViewCoordinator<ConnectToServerCoodinator> {
|
||||
NavigationViewCoordinator(ConnectToServerCoodinator())
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
SplashView()
|
||||
}
|
||||
}
|
||||
#elseif os(tvOS)
|
||||
// temp for fixing build error
|
||||
final class MainCoordinator: ViewCoordinatable {
|
||||
var children = ViewChild()
|
||||
// temp for fixing build error
|
||||
final class MainCoordinator: NavigationCoordinatable {
|
||||
var stack: NavigationStack<MainCoordinator>
|
||||
|
||||
enum Route: ViewRoute {
|
||||
case mainTab
|
||||
case connectToServer
|
||||
}
|
||||
@Root var mainTab = makeMainTab
|
||||
@Root var connectToServer = makeMainTab
|
||||
|
||||
func resolveRoute(route: Route) -> AnyCoordinatable {
|
||||
switch route {
|
||||
case .mainTab:
|
||||
return MainCoordinator().eraseToAnyCoordinatable()
|
||||
case .connectToServer:
|
||||
return MainCoordinator().eraseToAnyCoordinatable()
|
||||
func makeMainTab() -> NavigationViewCoordinator<MainTabCoordinator> {
|
||||
return NavigationViewCoordinator(MainTabCoordinator())
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
SplashView()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -13,36 +13,29 @@ import SwiftUI
|
|||
import Stinsen
|
||||
|
||||
final class MainTabCoordinator: TabCoordinatable {
|
||||
lazy var children = TabChild(self, tabRoutes: [.home, .allMedia])
|
||||
var child = TabChild(startingItems: [
|
||||
\MainTabCoordinator.home,
|
||||
\MainTabCoordinator.allMedia,
|
||||
])
|
||||
|
||||
enum Route: TabRoute {
|
||||
case home
|
||||
case allMedia
|
||||
@Route(tabItem: makeHomeTab) var home = makeHome
|
||||
@Route(tabItem: makeTodosTab) var allMedia = makeTodos
|
||||
|
||||
func makeHome() -> NavigationViewCoordinator<HomeCoordinator> {
|
||||
return NavigationViewCoordinator(HomeCoordinator())
|
||||
}
|
||||
|
||||
func tabItem(forTab tab: Int) -> some View {
|
||||
switch tab {
|
||||
case 0:
|
||||
Group {
|
||||
Text("Home")
|
||||
Image(systemName: "house")
|
||||
}
|
||||
case 1:
|
||||
Group {
|
||||
Text("Projects")
|
||||
Image(systemName: "folder")
|
||||
}
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
@ViewBuilder func makeHomeTab(isActive: Bool) -> some View {
|
||||
Image(systemName: "house")
|
||||
Text("Home")
|
||||
}
|
||||
|
||||
func resolveRoute(route: Route) -> AnyCoordinatable {
|
||||
switch route {
|
||||
case .home:
|
||||
return NavigationViewCoordinator(HomeCoordinator()).eraseToAnyCoordinatable()
|
||||
case .allMedia:
|
||||
return NavigationViewCoordinator(LibraryListCoordinator()).eraseToAnyCoordinatable()
|
||||
}
|
||||
func makeTodos() -> NavigationViewCoordinator<LibraryListCoordinator> {
|
||||
return NavigationViewCoordinator(LibraryListCoordinator())
|
||||
}
|
||||
|
||||
@ViewBuilder func makeTodosTab(isActive: Bool) -> some View {
|
||||
Image(systemName: "folder")
|
||||
Text("All Media")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,28 +10,25 @@
|
|||
import Foundation
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
import JellyfinAPI
|
||||
|
||||
final class SearchCoordinator: NavigationCoordinatable {
|
||||
var navigationStack = NavigationStack()
|
||||
let stack = NavigationStack(initial: \SearchCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var item = makeItem
|
||||
|
||||
var viewModel: LibrarySearchViewModel
|
||||
|
||||
init(viewModel: LibrarySearchViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
enum Route: NavigationRoute {
|
||||
case item(viewModel: ItemViewModel)
|
||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||
ItemCoordinator(item: item)
|
||||
}
|
||||
|
||||
func resolveRoute(route: Route) -> Transition {
|
||||
switch route {
|
||||
case let .item(viewModel):
|
||||
return .push(ItemCoordinator(viewModel: viewModel).eraseToAnyCoordinatable())
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
LibrarySearchView(viewModel: self.viewModel)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,21 +12,16 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
final class SettingsCoordinator: NavigationCoordinatable {
|
||||
var navigationStack = NavigationStack()
|
||||
let stack = NavigationStack(initial: \SettingsCoordinator.start)
|
||||
|
||||
enum Route: NavigationRoute {
|
||||
case serverDetail
|
||||
@Root var start = makeStart
|
||||
@Route(.push) var serverDetail = makeServerDetail
|
||||
|
||||
@ViewBuilder func makeServerDetail() -> some View {
|
||||
ServerDetailView()
|
||||
}
|
||||
|
||||
func resolveRoute(route: Route) -> Transition {
|
||||
switch route {
|
||||
case .serverDetail:
|
||||
return .push(ServerDetailView().eraseToAnyView())
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
SettingsView(viewModel: .init())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,19 +13,16 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
final class VideoPlayerCoordinator: NavigationCoordinatable {
|
||||
var navigationStack = NavigationStack()
|
||||
let stack = NavigationStack(initial: \VideoPlayerCoordinator.start)
|
||||
|
||||
@Root var start = makeStart
|
||||
var item: BaseItemDto
|
||||
|
||||
init(item: BaseItemDto) {
|
||||
self.item = item
|
||||
}
|
||||
|
||||
enum Route: NavigationRoute {}
|
||||
|
||||
func resolveRoute(route: Route) -> Transition {}
|
||||
|
||||
@ViewBuilder
|
||||
func start() -> some View {
|
||||
@ViewBuilder func makeStart() -> some View {
|
||||
VideoPlayerView(item: item)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ import Foundation
|
|||
import SwiftUI
|
||||
|
||||
struct HomeView: View {
|
||||
@EnvironmentObject var homeRouter: HomeCoordinator.Router
|
||||
@StateObject var viewModel = HomeViewModel()
|
||||
@State var showingSettings = false
|
||||
|
||||
|
||||
init() {
|
||||
let backButtonBackgroundImage = UIImage(systemName: "chevron.backward.circle.fill")
|
||||
let barAppearance = UINavigationBar.appearance()
|
||||
|
@ -43,16 +43,19 @@ struct HomeView: View {
|
|||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
Spacer()
|
||||
NavigationLink(destination: LazyView {
|
||||
LibraryView(viewModel: .init(parentID: libraryID, filters: viewModel.recentFilterSet), title: library?.name ?? "")
|
||||
}) {
|
||||
Button {
|
||||
homeRouter
|
||||
.route(to: \.library, (viewModel: .init(parentID: libraryID,
|
||||
filters: viewModel.recentFilterSet),
|
||||
title: library?.name ?? ""))
|
||||
} label: {
|
||||
HStack {
|
||||
Text("See All").font(.subheadline).fontWeight(.bold)
|
||||
Image(systemName: "chevron.right").font(Font.subheadline.bold())
|
||||
}
|
||||
}
|
||||
}.padding(.leading, 16)
|
||||
.padding(.trailing, 16)
|
||||
.padding(.trailing, 16)
|
||||
LatestMediaView(viewModel: .init(libraryID: libraryID))
|
||||
}
|
||||
}
|
||||
|
@ -68,14 +71,11 @@ struct HomeView: View {
|
|||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showingSettings = true
|
||||
homeRouter.route(to: \.settings)
|
||||
} label: {
|
||||
Image(systemName: "gear")
|
||||
}
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showingSettings) {
|
||||
SettingsView(viewModel: SettingsViewModel(), close: $showingSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,41 +5,41 @@
|
|||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import Introspect
|
||||
import JellyfinAPI
|
||||
import SwiftUI
|
||||
|
||||
class VideoPlayerItem: ObservableObject {
|
||||
@Published var shouldShowPlayer: Bool = false
|
||||
@Published var itemToPlay: BaseItemDto = BaseItemDto()
|
||||
@Published var itemToPlay = BaseItemDto()
|
||||
}
|
||||
|
||||
// Intermediary view for ItemView to set navigation bar settings
|
||||
struct ItemNavigationView: View {
|
||||
|
||||
private let item: BaseItemDto
|
||||
|
||||
|
||||
init(item: BaseItemDto) {
|
||||
self.item = item
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
ItemView(item: item)
|
||||
.navigationBarTitle("", displayMode: .inline)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct ItemView: View {
|
||||
private struct ItemView: View {
|
||||
@EnvironmentObject var itemRouter: ItemCoordinator.Router
|
||||
|
||||
@State private var videoIsLoading: Bool = false; // This variable is only changed by the underlying VLC view.
|
||||
@State private var videoIsLoading: Bool = false // This variable is only changed by the underlying VLC view.
|
||||
@State private var viewDidLoad: Bool = false
|
||||
@State private var orientation: UIDeviceOrientation = .unknown
|
||||
@StateObject private var videoPlayerItem: VideoPlayerItem = VideoPlayerItem()
|
||||
@StateObject private var videoPlayerItem = VideoPlayerItem()
|
||||
@Environment(\.horizontalSizeClass) private var hSizeClass
|
||||
@Environment(\.verticalSizeClass) private var vSizeClass
|
||||
|
||||
|
||||
private let viewModel: ItemViewModel
|
||||
|
||||
|
||||
init(item: BaseItemDto) {
|
||||
switch item.itemType {
|
||||
case .movie:
|
||||
|
@ -56,14 +56,20 @@ fileprivate struct ItemView: View {
|
|||
}
|
||||
|
||||
var body: some View {
|
||||
if hSizeClass == .compact && vSizeClass == .regular {
|
||||
ItemPortraitMainView(videoIsLoading: $videoIsLoading)
|
||||
.environmentObject(videoPlayerItem)
|
||||
.environmentObject(viewModel)
|
||||
} else {
|
||||
ItemLandscapeMainView(videoIsLoading: $videoIsLoading)
|
||||
.environmentObject(videoPlayerItem)
|
||||
.environmentObject(viewModel)
|
||||
Group {
|
||||
if hSizeClass == .compact && vSizeClass == .regular {
|
||||
ItemPortraitMainView(videoIsLoading: $videoIsLoading)
|
||||
.environmentObject(videoPlayerItem)
|
||||
.environmentObject(viewModel)
|
||||
} else {
|
||||
ItemLandscapeMainView(videoIsLoading: $videoIsLoading)
|
||||
.environmentObject(videoPlayerItem)
|
||||
.environmentObject(viewModel)
|
||||
}
|
||||
}
|
||||
.onReceive(videoPlayerItem.$shouldShowPlayer) { flag in
|
||||
guard flag else { return }
|
||||
self.itemRouter.route(to: \.videoPlayer, viewModel.item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
//
|
||||
/*
|
||||
* SwiftFin is subject to the terms of the Mozilla Public
|
||||
* License, v2.0. 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 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ItemLandscapeMainView: View {
|
||||
|
||||
@Binding private var videoIsLoading: Bool
|
||||
@EnvironmentObject private var viewModel: ItemViewModel
|
||||
@EnvironmentObject private var videoPlayerItem: VideoPlayerItem
|
||||
|
||||
|
||||
init(videoIsLoading: Binding<Bool>) {
|
||||
self._videoIsLoading = videoIsLoading
|
||||
}
|
||||
|
||||
|
||||
// MARK: innerBody
|
||||
|
||||
private var innerBody: some View {
|
||||
HStack {
|
||||
|
||||
// MARK: Sidebar Image
|
||||
|
||||
VStack {
|
||||
ImageView(src: viewModel.item.getPrimaryImage(maxWidth: 130),
|
||||
bh: viewModel.item.getPrimaryImageBlurHash())
|
||||
.frame(width: 130, height: 195)
|
||||
.cornerRadius(10)
|
||||
|
||||
|
||||
Spacer().frame(height: 15)
|
||||
|
||||
|
||||
Button {
|
||||
if let playButtonItem = viewModel.playButtonItem {
|
||||
self.videoPlayerItem.itemToPlay = playButtonItem
|
||||
self.videoPlayerItem.shouldShowPlayer = true
|
||||
}
|
||||
} label: {
|
||||
|
||||
// MARK: Play
|
||||
|
||||
HStack {
|
||||
Image(systemName: "play.fill")
|
||||
.foregroundColor(viewModel.playButtonItem == nil ? Color(UIColor.secondaryLabel) : Color.white)
|
||||
|
@ -53,18 +53,19 @@ struct ItemLandscapeMainView: View {
|
|||
.background(viewModel.playButtonItem == nil ? Color(UIColor.secondarySystemFill) : Color.jellyfinPurple)
|
||||
.cornerRadius(10)
|
||||
}.disabled(viewModel.playButtonItem == nil)
|
||||
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
// MARK: ItemLandscapeTopBarView
|
||||
|
||||
ItemLandscapeTopBarView()
|
||||
.environmentObject(viewModel)
|
||||
|
||||
|
||||
// MARK: ItemViewBody
|
||||
|
||||
if let episodeViewModel = viewModel as? SeasonItemViewModel {
|
||||
CardVStackView(items: episodeViewModel.episodes)
|
||||
} else {
|
||||
|
@ -75,32 +76,20 @@ struct ItemLandscapeMainView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: body
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
NavigationLink(destination: LoadingViewNoBlur(isShowing: $videoIsLoading) {
|
||||
VLCPlayerWithControls(item: videoPlayerItem.itemToPlay,
|
||||
loadBinding: $videoIsLoading,
|
||||
pBinding: _videoPlayerItem.projectedValue.shouldShowPlayer)
|
||||
.navigationBarHidden(true)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.statusBar(hidden: true)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.prefersHomeIndicatorAutoHidden(true)
|
||||
}, isActive: $videoPlayerItem.shouldShowPlayer) {
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
ZStack {
|
||||
|
||||
// MARK: Backdrop
|
||||
|
||||
ImageView(src: viewModel.item.getBackdropImage(maxWidth: 200),
|
||||
bh: viewModel.item.getBackdropImageBlurHash())
|
||||
.opacity(0.3)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.blur(radius: 4)
|
||||
|
||||
|
||||
// iPadOS is making the view go all the way to the edge.
|
||||
// We have to accomodate this here
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
|
|
|
@ -37,19 +37,6 @@ struct ItemPortraitMainView: View {
|
|||
// MARK: body
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
NavigationLink(destination: LoadingViewNoBlur(isShowing: $videoIsLoading) {
|
||||
VLCPlayerWithControls(item: videoPlayerItem.itemToPlay,
|
||||
loadBinding: $videoIsLoading,
|
||||
pBinding: _videoPlayerItem.projectedValue.shouldShowPlayer)
|
||||
.navigationBarHidden(true)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.statusBar(hidden: true)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.prefersHomeIndicatorAutoHidden(true)
|
||||
}, isActive: $videoPlayerItem.shouldShowPlayer) {
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
// MARK: ParallaxScrollView
|
||||
ParallaxHeaderScrollView(header: portraitHeaderView,
|
||||
staticOverlayView: portraitStaticOverlayView,
|
||||
|
|
|
@ -28,7 +28,7 @@ extension UIWindow {
|
|||
struct DeviceShakeViewModifier: ViewModifier {
|
||||
let action: () -> Void
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
func body(content: Self.Content) -> some View {
|
||||
content
|
||||
.onAppear()
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.deviceDidShakeNotification)) { _ in
|
||||
|
@ -228,7 +228,7 @@ struct JellyfinPlayerApp: App {
|
|||
})
|
||||
.withHostingWindow { window in
|
||||
window?
|
||||
.rootViewController = PreferenceUIHostingController(wrappedView: CoordinatorView(MainCoordinator())
|
||||
.rootViewController = PreferenceUIHostingController(wrappedView: MainCoordinator().view()
|
||||
.environment(\.managedObjectContext, persistenceController.container.viewContext))
|
||||
}
|
||||
.onShake {
|
||||
|
|
|
@ -9,7 +9,7 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
struct LatestMediaView: View {
|
||||
@EnvironmentObject var homeRouter: NavigationRouter<HomeCoordinator.Route>
|
||||
@EnvironmentObject var homeRouter: HomeCoordinator.Router
|
||||
@StateObject var viewModel: LatestMediaViewModel
|
||||
|
||||
var body: some View {
|
||||
|
@ -18,7 +18,7 @@ struct LatestMediaView: View {
|
|||
ForEach(viewModel.items, id: \.id) { item in
|
||||
if item.type == "Series" || item.type == "Movie" {
|
||||
Button {
|
||||
homeRouter.route(to: .item(viewModel: .init(id: item.id!)))
|
||||
homeRouter.route(to: \.item, item)
|
||||
} label: {
|
||||
PortraitItemView(item: item)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
|||
import Stinsen
|
||||
|
||||
struct LibraryFilterView: View {
|
||||
@EnvironmentObject var filterRouter: NavigationRouter<FilterCoordinator.Route>
|
||||
@EnvironmentObject var filterRouter: FilterCoordinator.Router
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@Binding var filters: LibraryFilters
|
||||
var parentId: String = ""
|
||||
|
@ -66,7 +66,7 @@ struct LibraryFilterView: View {
|
|||
Button {
|
||||
viewModel.resetFilters()
|
||||
self.filters = viewModel.modifiedFilters
|
||||
filterRouter.dismiss()
|
||||
filterRouter.dismissCoordinator()
|
||||
} label: {
|
||||
Text("Reset")
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ struct LibraryFilterView: View {
|
|||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
filterRouter.dismiss()
|
||||
filterRouter.dismissCoordinator()
|
||||
} label: {
|
||||
Image(systemName: "xmark")
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ struct LibraryFilterView: View {
|
|||
Button {
|
||||
viewModel.updateModifiedFilter()
|
||||
self.filters = viewModel.modifiedFilters
|
||||
filterRouter.dismiss()
|
||||
filterRouter.dismissCoordinator()
|
||||
} label: {
|
||||
Text("Apply")
|
||||
}
|
||||
|
|
|
@ -10,14 +10,15 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
struct LibraryListView: View {
|
||||
@EnvironmentObject var libraryListRouter: NavigationRouter<LibraryListCoordinator.Route>
|
||||
@EnvironmentObject var libraryListRouter: LibraryListCoordinator.Router
|
||||
@StateObject var viewModel = LibraryListViewModel()
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
Button {
|
||||
libraryListRouter.route(to: .library(viewModel: .init(filters: viewModel.withFavorites), title: "Favorites"))
|
||||
libraryListRouter.route(to: \.library,
|
||||
(viewModel: LibraryViewModel(filters: viewModel.withFavorites), title: "Favorites"))
|
||||
} label: {
|
||||
ZStack {
|
||||
HStack {
|
||||
|
@ -62,7 +63,9 @@ struct LibraryListView: View {
|
|||
ForEach(viewModel.libraries, id: \.id) { library in
|
||||
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
|
||||
Button {
|
||||
libraryListRouter.route(to: .library(viewModel: .init(parentID: library.id), title: library.name ?? ""))
|
||||
libraryListRouter.route(to: \.library,
|
||||
(viewModel: LibraryViewModel(parentID: library.id),
|
||||
title: library.name ?? ""))
|
||||
} label: {
|
||||
ZStack {
|
||||
ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
|
||||
|
@ -99,7 +102,7 @@ struct LibraryListView: View {
|
|||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
libraryListRouter.route(to: .search(viewModel: .init(parentID: nil)))
|
||||
libraryListRouter.route(to: \.search, LibrarySearchViewModel(parentID: nil))
|
||||
} label: {
|
||||
Image(systemName: "magnifyingglass")
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
struct LibrarySearchView: View {
|
||||
@EnvironmentObject var searchRouter: NavigationRouter<SearchCoordinator.Route>
|
||||
@EnvironmentObject var searchRouter: SearchCoordinator.Router
|
||||
@StateObject var viewModel: LibrarySearchViewModel
|
||||
@State private var searchQuery = ""
|
||||
|
||||
|
@ -81,7 +81,7 @@ struct LibrarySearchView: View {
|
|||
LazyVGrid(columns: tracks) {
|
||||
ForEach(items, id: \.id) { item in
|
||||
Button {
|
||||
searchRouter.route(to: .item(viewModel: .init(id: item.id!)))
|
||||
searchRouter.route(to: \.item, item)
|
||||
} label: {
|
||||
PortraitItemView(item: item)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
struct LibraryView: View {
|
||||
@EnvironmentObject var libraryRouter: NavigationRouter<LibraryCoordinator.Route>
|
||||
@EnvironmentObject var libraryRouter: LibraryCoordinator.Router
|
||||
@StateObject var viewModel: LibraryViewModel
|
||||
var title: String
|
||||
|
||||
|
@ -36,7 +36,7 @@ struct LibraryView: View {
|
|||
ForEach(viewModel.items, id: \.id) { item in
|
||||
if item.type != "Folder" {
|
||||
Button {
|
||||
libraryRouter.route(to: .item(viewModel: .init(id: item.id!)))
|
||||
libraryRouter.route(to: \.item, item)
|
||||
} label: {
|
||||
PortraitItemView(item: item)
|
||||
}
|
||||
|
@ -96,11 +96,11 @@ struct LibraryView: View {
|
|||
.foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange))
|
||||
.onTapGesture {
|
||||
libraryRouter
|
||||
.route(to: .filter(filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType,
|
||||
parentId: viewModel.parentID ?? ""))
|
||||
.route(to: \.filter, (filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType,
|
||||
parentId: viewModel.parentID ?? ""))
|
||||
}
|
||||
Button {
|
||||
libraryRouter.route(to: .search(viewModel: .init(parentID: viewModel.parentID)))
|
||||
libraryRouter.route(to: \.search, .init(parentID: viewModel.parentID))
|
||||
} label: {
|
||||
Image(systemName: "magnifyingglass")
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
struct NextUpView: View {
|
||||
@EnvironmentObject var homeRouter: NavigationRouter<HomeCoordinator.Route>
|
||||
@EnvironmentObject var homeRouter: HomeCoordinator.Router
|
||||
|
||||
var items: [BaseItemDto]
|
||||
|
||||
|
@ -25,7 +25,7 @@ struct NextUpView: View {
|
|||
LazyHStack {
|
||||
ForEach(items, id: \.id) { item in
|
||||
Button {
|
||||
homeRouter.route(to: .item(viewModel: .init(id: item.id!)))
|
||||
homeRouter.route(to: \.item, item)
|
||||
} label: {
|
||||
PortraitItemView(item: item)
|
||||
}
|
||||
|
|
|
@ -6,15 +6,16 @@
|
|||
*/
|
||||
|
||||
import CoreData
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import Stinsen
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
@EnvironmentObject var settingsRouter: SettingsCoordinator.Router
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
|
||||
@ObservedObject var viewModel: SettingsViewModel
|
||||
|
||||
@Binding var close: Bool
|
||||
@Default(.inNetworkBandwidth) var inNetworkStreamBitrate
|
||||
@Default(.outOfNetworkBandwidth) var outOfNetworkStreamBitrate
|
||||
@Default(.isAutoSelectSubtitles) var isAutoSelectSubtitles
|
||||
|
@ -25,101 +26,104 @@ struct SettingsView: View {
|
|||
@Default(.videoPlayerJumpBackward) var jumpBackwardLength
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section(header: EmptyView()) {
|
||||
Form {
|
||||
Section(header: EmptyView()) {
|
||||
HStack {
|
||||
Text("User")
|
||||
Spacer()
|
||||
Text(SessionManager.current.user.username ?? "")
|
||||
.foregroundColor(.jellyfinPurple)
|
||||
}
|
||||
|
||||
Button {
|
||||
settingsRouter.route(to: \.serverDetail)
|
||||
} label: {
|
||||
HStack {
|
||||
Text("User")
|
||||
Text("Server")
|
||||
Spacer()
|
||||
Text(SessionManager.current.user.username ?? "")
|
||||
Text(ServerEnvironment.current.server.name ?? "")
|
||||
.foregroundColor(.jellyfinPurple)
|
||||
}
|
||||
|
||||
NavigationLink(
|
||||
destination: ServerDetailView(),
|
||||
label: {
|
||||
HStack {
|
||||
Text("Server")
|
||||
Spacer()
|
||||
Text(ServerEnvironment.current.server.name ?? "")
|
||||
.foregroundColor(.jellyfinPurple)
|
||||
}
|
||||
})
|
||||
|
||||
Button {
|
||||
close = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
SessionManager.current.logout()
|
||||
let nc = NotificationCenter.default
|
||||
nc.post(name: Notification.Name("didSignOut"), object: nil)
|
||||
}
|
||||
} label: {
|
||||
Text("Sign out")
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
Section(header: Text("Playback")) {
|
||||
Picker("Default local quality", selection: $inNetworkStreamBitrate) {
|
||||
ForEach(self.viewModel.bitrates, id: \.self) { bitrate in
|
||||
Text(bitrate.name).tag(bitrate.value)
|
||||
}
|
||||
}
|
||||
|
||||
Picker("Default remote quality", selection: $outOfNetworkStreamBitrate) {
|
||||
ForEach(self.viewModel.bitrates, id: \.self) { bitrate in
|
||||
Text(bitrate.name).tag(bitrate.value)
|
||||
}
|
||||
}
|
||||
|
||||
Picker("Jump Forward Length", selection: $jumpForwardLength) {
|
||||
ForEach(self.viewModel.videoPlayerJumpLengths, id: \.self) { length in
|
||||
Text(length.label).tag(length.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
Picker("Jump Backward Length", selection: $jumpBackwardLength) {
|
||||
ForEach(self.viewModel.videoPlayerJumpLengths, id: \.self) { length in
|
||||
Text(length.label).tag(length.rawValue)
|
||||
}
|
||||
Image(systemName: "chevron.right")
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Accessibility")) {
|
||||
Toggle("Automatically show subtitles", isOn: $isAutoSelectSubtitles)
|
||||
SearchablePicker(label: "Preferred subtitle language",
|
||||
options: viewModel.langs,
|
||||
optionToString: { $0.name },
|
||||
selected: Binding<TrackLanguage>(
|
||||
get: { viewModel.langs.first(where: { $0.isoCode == autoSelectSubtitlesLangcode }) ?? .auto },
|
||||
set: {autoSelectSubtitlesLangcode = $0.isoCode}
|
||||
)
|
||||
)
|
||||
SearchablePicker(label: "Preferred audio language",
|
||||
options: viewModel.langs,
|
||||
optionToString: { $0.name },
|
||||
selected: Binding<TrackLanguage>(
|
||||
get: { viewModel.langs.first(where: { $0.isoCode == autoSelectAudioLangcode }) ?? .auto },
|
||||
set: { autoSelectAudioLangcode = $0.isoCode}
|
||||
)
|
||||
)
|
||||
Picker(NSLocalizedString("Appearance", comment: ""), selection: $appAppearance) {
|
||||
ForEach(self.viewModel.appearances, id: \.self) { appearance in
|
||||
Text(appearance.localizedName).tag(appearance.rawValue)
|
||||
}
|
||||
}.onChange(of: appAppearance, perform: { value in
|
||||
UIApplication.shared.windows.first?.overrideUserInterfaceStyle = appAppearance.style
|
||||
})
|
||||
Button {
|
||||
settingsRouter.dismissCoordinator()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
SessionManager.current.logout()
|
||||
let nc = NotificationCenter.default
|
||||
nc.post(name: Notification.Name("didSignOut"), object: nil)
|
||||
}
|
||||
} label: {
|
||||
Text("Sign out")
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Settings", displayMode: .inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
close = false
|
||||
} label: {
|
||||
Image(systemName: "xmark")
|
||||
Section(header: Text("Playback")) {
|
||||
Picker("Default local quality", selection: $inNetworkStreamBitrate) {
|
||||
ForEach(self.viewModel.bitrates, id: \.self) { bitrate in
|
||||
Text(bitrate.name).tag(bitrate.value)
|
||||
}
|
||||
}
|
||||
|
||||
Picker("Default remote quality", selection: $outOfNetworkStreamBitrate) {
|
||||
ForEach(self.viewModel.bitrates, id: \.self) { bitrate in
|
||||
Text(bitrate.name).tag(bitrate.value)
|
||||
}
|
||||
}
|
||||
|
||||
Picker("Jump Forward Length", selection: $jumpForwardLength) {
|
||||
ForEach(self.viewModel.videoPlayerJumpLengths, id: \.self) { length in
|
||||
Text(length.label).tag(length.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
Picker("Jump Backward Length", selection: $jumpBackwardLength) {
|
||||
ForEach(self.viewModel.videoPlayerJumpLengths, id: \.self) { length in
|
||||
Text(length.label).tag(length.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Accessibility")) {
|
||||
Toggle("Automatically show subtitles", isOn: $isAutoSelectSubtitles)
|
||||
SearchablePicker(label: "Preferred subtitle language",
|
||||
options: viewModel.langs,
|
||||
optionToString: { $0.name },
|
||||
selected: Binding<TrackLanguage>(get: {
|
||||
viewModel.langs
|
||||
.first(where: { $0.isoCode == autoSelectSubtitlesLangcode
|
||||
}) ??
|
||||
.auto
|
||||
},
|
||||
set: { autoSelectSubtitlesLangcode = $0.isoCode }))
|
||||
SearchablePicker(label: "Preferred audio language",
|
||||
options: viewModel.langs,
|
||||
optionToString: { $0.name },
|
||||
selected: Binding<TrackLanguage>(get: {
|
||||
viewModel.langs
|
||||
.first(where: { $0.isoCode == autoSelectAudioLangcode }) ??
|
||||
.auto
|
||||
},
|
||||
set: { autoSelectAudioLangcode = $0.isoCode }))
|
||||
Picker(NSLocalizedString("Appearance", comment: ""), selection: $appAppearance) {
|
||||
ForEach(self.viewModel.appearances, id: \.self) { appearance in
|
||||
Text(appearance.localizedName).tag(appearance.rawValue)
|
||||
}
|
||||
}.onChange(of: appAppearance, perform: { _ in
|
||||
UIApplication.shared.windows.first?.overrideUserInterfaceStyle = appAppearance.style
|
||||
})
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Settings", displayMode: .inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
settingsRouter.dismissCoordinator()
|
||||
} label: {
|
||||
Image(systemName: "xmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,16 +11,16 @@ import Stinsen
|
|||
import SwiftUI
|
||||
|
||||
struct SplashView: View {
|
||||
@EnvironmentObject var mainRouter: ViewRouter<MainCoordinator.Route>
|
||||
@EnvironmentObject var mainRouter: MainCoordinator.Router
|
||||
@StateObject var viewModel = SplashViewModel()
|
||||
|
||||
var body: some View {
|
||||
ProgressView()
|
||||
.onReceive(viewModel.$isLoggedIn) { flag in
|
||||
if flag {
|
||||
mainRouter.route(to: .mainTab)
|
||||
mainRouter.root(\.mainTab)
|
||||
} else {
|
||||
mainRouter.route(to: .connectToServer)
|
||||
mainRouter.root(\.connectToServer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ protocol PlayerViewControllerDelegate: AnyObject {
|
|||
|
||||
class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRemoteMediaClientListener {
|
||||
@RouterObject
|
||||
var main: ViewRouter<MainCoordinator.Route>?
|
||||
var main: MainCoordinator.Router?
|
||||
|
||||
weak var delegate: PlayerViewControllerDelegate?
|
||||
|
||||
|
@ -538,7 +538,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
case .error(401, _, _, _):
|
||||
self.delegate?.exitPlayer(self)
|
||||
SessionManager.current.logout()
|
||||
main?.route(to: .connectToServer)
|
||||
main?.root(\.connectToServer)
|
||||
case .error:
|
||||
self.delegate?.exitPlayer(self)
|
||||
}
|
||||
|
@ -1072,12 +1072,12 @@ struct VideoPlayerView: View {
|
|||
|
||||
struct VLCPlayerWithControls: UIViewControllerRepresentable {
|
||||
var item: BaseItemDto
|
||||
@RouterObject var playerRouter: NavigationRouter<VideoPlayerCoordinator.Route>?
|
||||
@RouterObject var playerRouter: VideoPlayerCoordinator.Router?
|
||||
|
||||
let loadBinding: Binding<Bool>
|
||||
|
||||
class Coordinator: NSObject, PlayerViewControllerDelegate {
|
||||
let parent: VLCPlayerWithControls
|
||||
var parent: VLCPlayerWithControls
|
||||
let loadBinding: Binding<Bool>
|
||||
|
||||
init(parent: VLCPlayerWithControls, loadBinding: Binding<Bool>) {
|
||||
|
@ -1094,7 +1094,7 @@ struct VLCPlayerWithControls: UIViewControllerRepresentable {
|
|||
}
|
||||
|
||||
func exitPlayer(_ viewController: PlayerViewController) {
|
||||
parent.playerRouter?.dismiss()
|
||||
parent.playerRouter?.dismissCoordinator()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ final class AppURLHandler {
|
|||
static let deepLinkScheme = "jellyfin"
|
||||
|
||||
@RouterObject
|
||||
var router: NavigationRouter<HomeCoordinator.Route>?
|
||||
var router: HomeCoordinator.Router?
|
||||
|
||||
enum AppURLState {
|
||||
case launched
|
||||
|
@ -54,7 +54,7 @@ extension AppURLHandler {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
func processLaunchedURLIfNeeded() {
|
||||
guard let launchURL = launchURL else { return }
|
||||
if processDeepLink(url: launchURL) {
|
||||
|
@ -78,7 +78,7 @@ extension AppURLHandler {
|
|||
if url.pathComponents[safe: 2]?.lowercased() == "items",
|
||||
let itemID = url.pathComponents[safe: 3]
|
||||
{
|
||||
router?.route(to: .item(viewModel: .init(id: itemID)))
|
||||
// router?.route(to: \.item(item: item))
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import Stinsen
|
|||
|
||||
final class ConnectToServerViewModel: ViewModel {
|
||||
@RouterObject
|
||||
var main: ViewRouter<MainCoordinator.Route>?
|
||||
var main: MainCoordinator.Router?
|
||||
|
||||
@Published var isConnectedServer = false
|
||||
|
||||
|
@ -60,13 +60,12 @@ final class ConnectToServerViewModel: ViewModel {
|
|||
}
|
||||
|
||||
func connectToServer() {
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
if uriSubject.value == "localhost" {
|
||||
uriSubject.value = "http://localhost:8096"
|
||||
}
|
||||
if uriSubject.value == "localhost" {
|
||||
uriSubject.value = "http://localhost:8096"
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
LogManager.shared.log.debug("Attempting to connect to server at \"\(uriSubject.value)\"", tag: "connectToServer")
|
||||
ServerEnvironment.current.create(with: uriSubject.value)
|
||||
.trackActivity(loading)
|
||||
|
@ -112,7 +111,7 @@ final class ConnectToServerViewModel: ViewModel {
|
|||
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "login",
|
||||
completion: completion)
|
||||
}, receiveValue: { [weak self] _ in
|
||||
self?.main?.route(to: .mainTab)
|
||||
self?.main?.root(\.mainTab)
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue