some fixes for TabView getting cut off when scrolling out of viewport.
This commit is contained in:
parent
b3b49bf81b
commit
ac26ac8077
|
@ -45,8 +45,8 @@ struct LandscapeItemElement: View {
|
|||
|
||||
var body: some View {
|
||||
VStack() {
|
||||
ImageView(src: item.getBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 375), bh: item.getBackdropImageBlurHash())
|
||||
.frame(width: 375, height: 250)
|
||||
ImageView(src: (item.type == "Episode" ? item.getSeriesBackdropImage(maxWidth: 445) : item.getBackdropImage(maxWidth: 445)), bh: item.type == "Episode" ? item.getSeriesBackdropImageBlurHash() : item.getBackdropImageBlurHash())
|
||||
.frame(width: 445, height: 250)
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
Group {
|
||||
|
@ -54,7 +54,7 @@ struct LandscapeItemElement: View {
|
|||
ZStack(alignment: .leading) {
|
||||
Rectangle()
|
||||
.fill(LinearGradient(colors: [.black,.clear], startPoint: .bottom, endPoint: .top))
|
||||
.frame(width: 375, height: 90)
|
||||
.frame(width: 445, height: 90)
|
||||
.mask(CutOffShadow())
|
||||
VStack(alignment: .leading) {
|
||||
Text("CONTINUE • \(item.getItemProgressString())")
|
||||
|
@ -68,9 +68,9 @@ struct LandscapeItemElement: View {
|
|||
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 12, maxHeight: 12)
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(Color(red: 172/255, green: 92/255, blue: 195/255))
|
||||
.frame(width: CGFloat(item.userData?.playedPercentage ?? 0 * 3.59), height: 12)
|
||||
.frame(width: CGFloat(item.userData?.playedPercentage ?? 0 * 4.45 - 0.16), height: 12)
|
||||
}
|
||||
}.padding(8)
|
||||
}.padding(12)
|
||||
}
|
||||
} else {
|
||||
EmptyView()
|
||||
|
@ -80,11 +80,11 @@ struct LandscapeItemElement: View {
|
|||
.shadow(radius: focused ? 10.0 : 0, y: focused ? 10.0 : 0)
|
||||
.shadow(radius: focused ? 10.0 : 0, y: focused ? 10.0 : 0)
|
||||
if(focused) {
|
||||
Text(item.seriesName ?? item.name ?? "")
|
||||
Text(item.type == "Episode" ? "\(item.seriesName ?? "") • S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0))" : item.name ?? "")
|
||||
.font(.callout)
|
||||
.fontWeight(.semibold)
|
||||
.lineLimit(1)
|
||||
.frame(width: 375)
|
||||
.frame(width: 445)
|
||||
} else {
|
||||
Spacer().frame(height: 25)
|
||||
}
|
||||
|
@ -95,12 +95,11 @@ struct LandscapeItemElement: View {
|
|||
}
|
||||
|
||||
if(envFocus == true) {
|
||||
backgroundURL = item.getBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: Int((UIScreen.main.currentMode?.size.width)!))
|
||||
BackgroundManager.current.setBackground(to: backgroundURL!, hash: item.getBackdropImageBlurHash())
|
||||
} else {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
if(BackgroundManager.current.backgroundURL == backgroundURL) {
|
||||
BackgroundManager.current.clearBackground()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
// your code here
|
||||
if(self.focused == true) {
|
||||
backgroundURL = item.getBackdropImage(maxWidth: 1080)
|
||||
BackgroundManager.current.setBackground(to: backgroundURL!, hash: item.getBackdropImageBlurHash())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
/*
|
||||
* SwiftFin is subject to the terms of the Mozilla Public
|
||||
* License, v2.0. 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
|
||||
|
||||
fileprivate struct CutOffShadow: Shape {
|
||||
let radius = 6.0;
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var path = Path()
|
||||
|
||||
let tl = CGPoint(x: rect.minX, y: rect.minY)
|
||||
let tr = CGPoint(x: rect.maxX, y: rect.minY)
|
||||
let brs = CGPoint(x: rect.maxX, y: rect.maxY - radius)
|
||||
let brc = CGPoint(x: rect.maxX - radius, y: rect.maxY - radius)
|
||||
let bls = CGPoint(x: rect.minX + radius, y: rect.maxY)
|
||||
let blc = CGPoint(x: rect.minX + radius, y: rect.maxY - radius)
|
||||
|
||||
path.move(to: tl)
|
||||
path.addLine(to: tr)
|
||||
path.addLine(to: brs)
|
||||
path.addRelativeArc(center: brc, radius: radius,
|
||||
startAngle: Angle.degrees(0), delta: Angle.degrees(90))
|
||||
path.addLine(to: bls)
|
||||
path.addRelativeArc(center: blc, radius: radius,
|
||||
startAngle: Angle.degrees(90), delta: Angle.degrees(90))
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
struct PortraitItemElement: View {
|
||||
@Environment(\.isFocused) var envFocused: Bool
|
||||
@State var focused: Bool = false;
|
||||
@State var backgroundURL: URL?;
|
||||
|
||||
var item: BaseItemDto;
|
||||
|
||||
var body: some View {
|
||||
VStack() {
|
||||
ImageView(src: item.type == "Episode" ? item.getSeriesPrimaryImage(maxWidth: 200) : item.getPrimaryImage(maxWidth: 200), bh: item.type == "Episode" ? item.getSeriesPrimaryImageBlurHash() : item.getPrimaryImageBlurHash())
|
||||
.frame(width: 200, height: 300)
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: focused ? 10.0 : 0)
|
||||
.shadow(radius: focused ? 10.0 : 0)
|
||||
}
|
||||
.onChange(of: envFocused) { envFocus in
|
||||
withAnimation(.linear(duration: 0.15)) {
|
||||
self.focused = envFocus
|
||||
}
|
||||
|
||||
if(envFocus == true) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
// your code here
|
||||
if(self.focused == true) {
|
||||
backgroundURL = item.getBackdropImage(maxWidth: 1080)
|
||||
BackgroundManager.current.setBackground(to: backgroundURL!, hash: item.getBackdropImageBlurHash())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.scaleEffect(focused ? 1.1 : 1)
|
||||
}
|
||||
}
|
|
@ -0,0 +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
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import JellyfinAPI
|
||||
import CoreMedia
|
||||
|
||||
struct PublicUserButton: View {
|
||||
@Environment(\.isFocused) var envFocused: Bool
|
||||
@State var focused: Bool = false;
|
||||
var publicUser: UserDto
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if publicUser.primaryImageTag != nil {
|
||||
ImageView(src: URL(string: "\(ServerEnvironment.current.server.baseURI ?? "")/Users/\(publicUser.id ?? "")/Images/Primary?width=500&quality=80&tag=\(publicUser.primaryImageTag!)")!)
|
||||
.frame(width: 250, height: 250)
|
||||
.cornerRadius(125.0)
|
||||
} else {
|
||||
Image(systemName: "person.fill")
|
||||
.foregroundColor(Color(red: 1, green: 1, blue: 1).opacity(0.8))
|
||||
.font(.system(size: 35))
|
||||
.frame(width: 250, height: 250)
|
||||
.background(Color(red: 98 / 255, green: 121 / 255, blue: 205 / 255))
|
||||
.cornerRadius(125.0)
|
||||
.shadow(radius: 6)
|
||||
}
|
||||
if(focused) {
|
||||
Text(publicUser.name ?? "").font(.headline).fontWeight(.semibold)
|
||||
} else {
|
||||
Spacer().frame(height: 60)
|
||||
}
|
||||
}.onChange(of: envFocused) { envFocus in
|
||||
withAnimation(.linear(duration: 0.15)) {
|
||||
self.focused = envFocus
|
||||
}
|
||||
}.scaleEffect(focused ? 1.1 : 1)
|
||||
}
|
||||
}
|
|
@ -83,25 +83,10 @@ struct ConnectToServerView: View {
|
|||
}
|
||||
}
|
||||
}) {
|
||||
VStack {
|
||||
if publicUser.primaryImageTag != nil {
|
||||
ImageView(src: URL(string: "\(ServerEnvironment.current.server.baseURI ?? "")/Users/\(publicUser.id ?? "")/Images/Primary?width=500&quality=80&tag=\(publicUser.primaryImageTag!)")!)
|
||||
.frame(width: 250, height: 250)
|
||||
.cornerRadius(125.0)
|
||||
} else {
|
||||
Image(systemName: "person.fill")
|
||||
.foregroundColor(Color(red: 1, green: 1, blue: 1).opacity(0.8))
|
||||
.font(.system(size: 35))
|
||||
.frame(width: 250, height: 250)
|
||||
.background(Color(red: 98 / 255, green: 121 / 255, blue: 205 / 255))
|
||||
.cornerRadius(125.0)
|
||||
.shadow(radius: 6)
|
||||
}
|
||||
Text(publicUser.name ?? "").font(.headline).fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
PublicUserButton(publicUser: publicUser)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
}
|
||||
}
|
||||
}.padding(.bottom, 20)
|
||||
HStack() {
|
||||
Spacer()
|
||||
Button {
|
||||
|
|
|
@ -12,6 +12,7 @@ import Combine
|
|||
|
||||
struct ContinueWatchingView: View {
|
||||
var items: [BaseItemDto]
|
||||
@Namespace private var namespace
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
|
@ -27,11 +28,11 @@ struct ContinueWatchingView: View {
|
|||
NavigationLink(destination: Text("itemv")) {
|
||||
LandscapeItemElement(item: item)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
.prefersDefaultFocus(item == items.first, in: namespace)
|
||||
}
|
||||
Spacer().frame(width: 90)
|
||||
}
|
||||
}.frame(height: 330)
|
||||
.offset(y: -10)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
|
|
|
@ -27,34 +27,24 @@ struct HomeView: View {
|
|||
if !viewModel.nextUpItems.isEmpty {
|
||||
NextUpView(items: viewModel.nextUpItems)
|
||||
}
|
||||
/*
|
||||
|
||||
if !viewModel.librariesShowRecentlyAddedIDs.isEmpty {
|
||||
ForEach(viewModel.librariesShowRecentlyAddedIDs, id: \.self) { libraryID in
|
||||
VStack(alignment: .leading) {
|
||||
let library = viewModel.libraries.first(where: { $0.id == libraryID })
|
||||
HStack {
|
||||
Text("Latest \(library?.name ?? "")")
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
|
||||
Spacer()
|
||||
NavigationLink(destination: LazyView {
|
||||
//LibraryView(usingParentID: libraryID, title: library?.name ?? "", usingFilters: viewModel.recentFilterSet)
|
||||
Text("library here")
|
||||
}) {
|
||||
HStack {
|
||||
Text("See All").font(.subheadline).fontWeight(.bold)
|
||||
Image(systemName: "chevron.right").font(Font.subheadline.bold())
|
||||
}
|
||||
|
||||
NavigationLink(destination: Text("library_latest")) {
|
||||
HStack() {
|
||||
Text("Latest \(library?.name ?? "")")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
Image(systemName: "chevron.forward.circle.fill")
|
||||
}
|
||||
}.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
||||
//LatestMediaView(usingParentID: libraryID)
|
||||
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
||||
}.padding(EdgeInsets(top: 0, leading: 135, bottom: 0, trailing: 0))
|
||||
LatestMediaView(usingParentID: libraryID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer().frame(height: 16)
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
struct JellyfinPlayer_tvOSApp: App {
|
||||
|
@ -15,8 +16,7 @@ struct JellyfinPlayer_tvOSApp: App {
|
|||
WindowGroup {
|
||||
SplashView()
|
||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
.padding(EdgeInsets(top: 0, leading: -90, bottom: 0, trailing: -90))
|
||||
.ignoresSafeArea(.all)
|
||||
.ignoresSafeArea(.all, edges: .all)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/* 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
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import JellyfinAPI
|
||||
import Combine
|
||||
|
||||
struct LatestMediaView: View {
|
||||
|
||||
@StateObject
|
||||
var tempViewModel = ViewModel()
|
||||
@State var items: [BaseItemDto] = []
|
||||
private var library_id: String = ""
|
||||
@State private var viewDidLoad: Bool = false
|
||||
|
||||
init(usingParentID: String) {
|
||||
library_id = usingParentID
|
||||
}
|
||||
|
||||
func onAppear() {
|
||||
if viewDidLoad == true {
|
||||
return
|
||||
}
|
||||
viewDidLoad = true
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
UserLibraryAPI.getLatestMedia(userId: SessionManager.current.user.user_id!, parentId: library_id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], enableUserData: true, limit: 12)
|
||||
.sink(receiveCompletion: { completion in
|
||||
print(completion)
|
||||
}, receiveValue: { response in
|
||||
items = response
|
||||
})
|
||||
.store(in: &tempViewModel.cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
LazyHStack {
|
||||
Spacer().frame(width: 90)
|
||||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: Text("itemv")) {
|
||||
PortraitItemElement(item: item)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
}
|
||||
Spacer().frame(width: 90)
|
||||
}
|
||||
}.frame(height: 350)
|
||||
.onAppear(perform: onAppear)
|
||||
}
|
||||
}
|
|
@ -13,39 +13,34 @@ import SwiftUI
|
|||
struct MainTabView: View {
|
||||
@State private var tabSelection: Tab = .home
|
||||
@StateObject private var viewModel = MainTabViewModel()
|
||||
@State private var backdropAnim: Bool = false
|
||||
@State private var backdropAnim: Bool = true
|
||||
@State private var lastBackdropAnim: Bool = false
|
||||
|
||||
var body: some View {
|
||||
ZStack() {
|
||||
//please do not touch my magical crossfading.
|
||||
//please do not touch my magical crossfading. i will wave my magical github wand and cry
|
||||
if(viewModel.lastBackgroundURL != nil) {
|
||||
ImageView(src: viewModel.lastBackgroundURL!, bh: viewModel.backgroundBlurHash)
|
||||
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
|
||||
.opacity(lastBackdropAnim ? 0.4 : 0)
|
||||
}
|
||||
if(viewModel.backgroundURL != nil) {
|
||||
if(viewModel.lastBackgroundURL != nil) {
|
||||
ImageView(src: viewModel.lastBackgroundURL!, bh: viewModel.backgroundBlurHash)
|
||||
.frame(width: UIScreen.main.currentMode?.size.width, height: UIScreen.main.currentMode?.size.height)
|
||||
.blur(radius: 2)
|
||||
.opacity(lastBackdropAnim ? 0.4 : 0)
|
||||
.onChange(of: viewModel.backgroundURL) { _ in
|
||||
withAnimation(.linear(duration: 0.15)) {
|
||||
lastBackdropAnim = false
|
||||
}
|
||||
}
|
||||
}
|
||||
ImageView(src: viewModel.backgroundURL!, bh: viewModel.backgroundBlurHash)
|
||||
.frame(width: UIScreen.main.currentMode?.size.width, height: UIScreen.main.currentMode?.size.height)
|
||||
.blur(radius: 2)
|
||||
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
|
||||
.opacity(backdropAnim ? 0.4 : 0)
|
||||
.onChange(of: viewModel.backgroundURL) { _ in
|
||||
lastBackdropAnim = true
|
||||
backdropAnim = false
|
||||
withAnimation(.linear(duration: 0.15)) {
|
||||
withAnimation(.linear(duration: 0.33)) {
|
||||
lastBackdropAnim = false
|
||||
backdropAnim = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TabView(selection: $tabSelection) {
|
||||
HomeView()
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.offset(y: -20)
|
||||
.tabItem {
|
||||
Text(Tab.home.localized)
|
||||
Image(systemName: "house")
|
||||
|
@ -53,7 +48,6 @@ struct MainTabView: View {
|
|||
.tag(Tab.home)
|
||||
|
||||
Text("Library")
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.tabItem {
|
||||
Text(Tab.allMedia.localized)
|
||||
Image(systemName: "folder")
|
||||
|
|
|
@ -17,9 +17,7 @@ struct SplashView: View {
|
|||
if viewModel.isLoggedIn {
|
||||
NavigationView() {
|
||||
MainTabView()
|
||||
}
|
||||
.padding(.leading, -60)
|
||||
.padding(.trailing, -60)
|
||||
}.padding(.all, -1)
|
||||
} else {
|
||||
NavigationView {
|
||||
ConnectToServerView()
|
||||
|
|
|
@ -13,9 +13,6 @@
|
|||
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690EE267ABF72005D8AB9 /* NextUpView.swift */; };
|
||||
531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */; };
|
||||
531690FA267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */; };
|
||||
531690FD267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */; };
|
||||
531690FE267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */; };
|
||||
531690FF267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */; };
|
||||
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; };
|
||||
531ABF6C2671F5CC00C0FE20 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 628B95212670CABD0091AF3B /* WidgetKit.framework */; };
|
||||
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; };
|
||||
|
@ -50,6 +47,10 @@
|
|||
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB57B2678CE1000530A6E /* ViewModel.swift */; };
|
||||
536D3D79267BD5D00004248C /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB57B2678CE1000530A6E /* ViewModel.swift */; };
|
||||
536D3D7D267BD5F90004248C /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 536D3D7C267BD5F90004248C /* ActivityIndicator */; };
|
||||
536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D7E267BDF100004248C /* LatestMediaView.swift */; };
|
||||
536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D80267BDFC60004248C /* PortraitItemElement.swift */; };
|
||||
536D3D84267BEA550004248C /* ParallaxView in Frameworks */ = {isa = PBXBuildFile; productRef = 536D3D83267BEA550004248C /* ParallaxView */; };
|
||||
536D3D88267C17350004248C /* PublicUserButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D87267C17350004248C /* PublicUserButton.swift */; };
|
||||
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */; };
|
||||
5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* Assets.xcassets */; };
|
||||
5377CBFC263B596B003A4E83 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBFB263B596B003A4E83 /* Preview Assets.xcassets */; };
|
||||
|
@ -191,7 +192,6 @@
|
|||
531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandscapeItemElement.swift; sourceTree = "<group>"; };
|
||||
531690F8267AD135005D8AB9 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlainNavigationLinkButton.swift; sourceTree = "<group>"; };
|
||||
531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleAPIRequestCompletion.swift; sourceTree = "<group>"; };
|
||||
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = "<group>"; };
|
||||
531AC8BE26750DE20091C7EB /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
|
||||
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -209,6 +209,9 @@
|
|||
5364F454266CA0DC0026ECBA /* APIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIExtensions.swift; sourceTree = "<group>"; };
|
||||
536D3D73267BA8170004248C /* BackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundManager.swift; sourceTree = "<group>"; };
|
||||
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabViewModel.swift; sourceTree = "<group>"; };
|
||||
536D3D7E267BDF100004248C /* LatestMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaView.swift; sourceTree = "<group>"; };
|
||||
536D3D80267BDFC60004248C /* PortraitItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemElement.swift; sourceTree = "<group>"; };
|
||||
536D3D87267C17350004248C /* PublicUserButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicUserButton.swift; sourceTree = "<group>"; };
|
||||
5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JellyfinPlayer iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinPlayerApp.swift; sourceTree = "<group>"; };
|
||||
5377CBF8263B596B003A4E83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
|
@ -277,6 +280,7 @@
|
|||
535870912669D7A800D05A09 /* Introspect in Frameworks */,
|
||||
625CB57E2678E81E00530A6E /* TVVLCKit.xcframework in Frameworks */,
|
||||
5358708D2669D7A800D05A09 /* KeychainSwift in Frameworks */,
|
||||
536D3D84267BEA550004248C /* ParallaxView in Frameworks */,
|
||||
53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */,
|
||||
5358709B2669D7A800D05A09 /* NukeUI in Frameworks */,
|
||||
53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */,
|
||||
|
@ -344,6 +348,7 @@
|
|||
531690E4267ABD5C005D8AB9 /* MainTabView.swift */,
|
||||
531690E6267ABD79005D8AB9 /* HomeView.swift */,
|
||||
531690F8267AD135005D8AB9 /* README.md */,
|
||||
536D3D7E267BDF100004248C /* LatestMediaView.swift */,
|
||||
);
|
||||
path = "JellyfinPlayer tvOS";
|
||||
sourceTree = "<group>";
|
||||
|
@ -380,6 +385,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */,
|
||||
536D3D80267BDFC60004248C /* PortraitItemElement.swift */,
|
||||
536D3D87267C17350004248C /* PublicUserButton.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
|
@ -476,7 +483,6 @@
|
|||
6267B3D526710B8900A7371D /* CollectionExtensions.swift */,
|
||||
6267B3D92671138200A7371D /* ImageExtensions.swift */,
|
||||
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */,
|
||||
531690FC267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -535,6 +541,7 @@
|
|||
5358709A2669D7A800D05A09 /* NukeUI */,
|
||||
53A431BE266B0FFE0016769F /* JellyfinAPI */,
|
||||
53ABFDEC26799D7700886593 /* ActivityIndicator */,
|
||||
536D3D83267BEA550004248C /* ParallaxView */,
|
||||
);
|
||||
productName = "JellyfinPlayer tvOS";
|
||||
productReference = 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */;
|
||||
|
@ -629,6 +636,7 @@
|
|||
621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */,
|
||||
53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */,
|
||||
625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */,
|
||||
536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */,
|
||||
);
|
||||
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -684,6 +692,8 @@
|
|||
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */,
|
||||
53ABFDDE267974E300886593 /* SplashView.swift in Sources */,
|
||||
53ABFDE8267974EF00886593 /* SplashViewModel.swift in Sources */,
|
||||
536D3D88267C17350004248C /* PublicUserButton.swift in Sources */,
|
||||
536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */,
|
||||
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */,
|
||||
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */,
|
||||
531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */,
|
||||
|
@ -693,12 +703,12 @@
|
|||
535870A52669D8AE00D05A09 /* ParallaxHeader.swift in Sources */,
|
||||
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */,
|
||||
535870A72669D8AE00D05A09 /* MultiSelectorView.swift in Sources */,
|
||||
536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */,
|
||||
531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */,
|
||||
53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */,
|
||||
53ABFDEE26799DCD00886593 /* ImageView.swift in Sources */,
|
||||
5358706C2669D21700D05A09 /* PersistenceController.swift in Sources */,
|
||||
535870AA2669D8AE00D05A09 /* BlurHashDecode.swift in Sources */,
|
||||
531690FE267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */,
|
||||
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
|
||||
535870A62669D8AE00D05A09 /* LazyView.swift in Sources */,
|
||||
5321753E2671DE9C005491E6 /* Typings.swift in Sources */,
|
||||
|
@ -741,7 +751,6 @@
|
|||
5377CC01263B596B003A4E83 /* Model.xcdatamodeld in Sources */,
|
||||
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */,
|
||||
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
|
||||
531690FD267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */,
|
||||
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */,
|
||||
625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */,
|
||||
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
|
||||
|
@ -780,7 +789,6 @@
|
|||
628B95382670CDAB0091AF3B /* Model.xcdatamodeld in Sources */,
|
||||
62EC353226766849000E9F2D /* SessionManager.swift in Sources */,
|
||||
536D3D79267BD5D00004248C /* ViewModel.swift in Sources */,
|
||||
531690FF267AEDC5005D8AB9 /* HandleAPIRequestCompletion.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1155,6 +1163,14 @@
|
|||
minimumVersion = 19.0.0;
|
||||
};
|
||||
};
|
||||
536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/PGSSoft/ParallaxView";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 3.0.0;
|
||||
};
|
||||
};
|
||||
53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/jellyfin/jellyfin-sdk-swift";
|
||||
|
@ -1212,6 +1228,11 @@
|
|||
package = 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */;
|
||||
productName = ActivityIndicator;
|
||||
};
|
||||
536D3D83267BEA550004248C /* ParallaxView */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */;
|
||||
productName = ParallaxView;
|
||||
};
|
||||
53A431BC266B0FF20016769F /* JellyfinAPI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */;
|
||||
|
|
|
@ -64,6 +64,15 @@
|
|||
"version": "0.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "ParallaxView",
|
||||
"repositoryURL": "https://github.com/PGSSoft/ParallaxView",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "a4165b0edd9c9c923a1d6e3e4c9a807302a1a475",
|
||||
"version": "3.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftUI-Introspect",
|
||||
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect",
|
||||
|
|
|
@ -43,7 +43,7 @@ struct ContinueWatchingView: View {
|
|||
NavigationLink(destination: ItemView(item: item)) {
|
||||
VStack(alignment: .leading) {
|
||||
Spacer().frame(height: 10)
|
||||
ImageView(src: item.getBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 320), bh: item.getBackdropImageBlurHash())
|
||||
ImageView(src: item.getBackdropImage(maxWidth: 320), bh: item.getBackdropImageBlurHash())
|
||||
.frame(width: 320, height: 180)
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
|
|
|
@ -66,7 +66,7 @@ struct EpisodeItemView: View {
|
|||
}
|
||||
|
||||
var portraitHeaderView: some View {
|
||||
ImageView(src: item.getBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: UIDevice.current.userInterfaceIdiom == .pad ? 622 : Int(UIScreen.main.bounds.width)), bh: item.getBackdropImageBlurHash())
|
||||
ImageView(src: item.getBackdropImage(maxWidth: UIDevice.current.userInterfaceIdiom == .pad ? 622 : Int(UIScreen.main.bounds.width)), bh: item.getBackdropImageBlurHash())
|
||||
.opacity(0.4)
|
||||
.blur(radius: 2.0)
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ struct EpisodeItemView: View {
|
|||
var portraitHeaderOverlayView: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .bottom, spacing: 12) {
|
||||
ImageView(src: item.getSeriesPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 120), bh: item.getSeriesPrimaryImageBlurHash())
|
||||
ImageView(src: item.getSeriesPrimaryImage(maxWidth: 120), bh: item.getSeriesPrimaryImageBlurHash())
|
||||
.frame(width: 120, height: 180)
|
||||
.cornerRadius(10)
|
||||
VStack(alignment: .leading) {
|
||||
|
@ -233,7 +233,7 @@ struct EpisodeItemView: View {
|
|||
} else {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
ImageView(src: item.getBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 200), bh: item.getBackdropImageBlurHash())
|
||||
ImageView(src: item.getBackdropImage(maxWidth: 200), bh: item.getBackdropImageBlurHash())
|
||||
.opacity(0.3)
|
||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing,
|
||||
height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom)
|
||||
|
@ -241,7 +241,7 @@ struct EpisodeItemView: View {
|
|||
.blur(radius: 4)
|
||||
HStack {
|
||||
VStack {
|
||||
ImageView(src: item.getSeriesPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 120), bh: item.getSeriesPrimaryImageBlurHash())
|
||||
ImageView(src: item.getSeriesPrimaryImage(maxWidth: 120), bh: item.getSeriesPrimaryImageBlurHash())
|
||||
.frame(width: 120, height: 180)
|
||||
.cornerRadius(10)
|
||||
Spacer().frame(height: 15)
|
||||
|
|
|
@ -47,7 +47,7 @@ struct LatestMediaView: View {
|
|||
NavigationLink(destination: ItemView(item: item)) {
|
||||
VStack(alignment: .leading) {
|
||||
Spacer().frame(height: 10)
|
||||
ImageView(src: item.getPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 100), bh: item.getPrimaryImageBlurHash())
|
||||
ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash())
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
Spacer().frame(height: 5)
|
||||
|
|
|
@ -68,7 +68,7 @@ struct LibrarySearchView: View {
|
|||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
VStack(alignment: .leading) {
|
||||
ImageView(src: item.getPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 100), bh: item.getPrimaryImageBlurHash())
|
||||
ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash())
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
Text(item.name ?? "")
|
||||
|
|
|
@ -108,7 +108,7 @@ struct LibraryView: View {
|
|||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
VStack(alignment: .leading) {
|
||||
ImageView(src: item.getPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 100), bh: item.getPrimaryImageBlurHash())
|
||||
ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash())
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
Text(item.name ?? "")
|
||||
|
|
|
@ -73,8 +73,7 @@ struct MovieItemView: View {
|
|||
|
||||
var portraitHeaderView: some View {
|
||||
ImageView(src: item
|
||||
.getBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!,
|
||||
maxWidth: UIDevice.current.userInterfaceIdiom == .pad ? 622 : Int(UIScreen.main.bounds.width)),
|
||||
.getBackdropImage(maxWidth: UIDevice.current.userInterfaceIdiom == .pad ? 622 : Int(UIScreen.main.bounds.width)),
|
||||
bh: item.getBackdropImageBlurHash())
|
||||
.opacity(0.4)
|
||||
.blur(radius: 2.0)
|
||||
|
@ -83,7 +82,7 @@ struct MovieItemView: View {
|
|||
var portraitHeaderOverlayView: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .bottom, spacing: 12) {
|
||||
ImageView(src: item.getPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 120))
|
||||
ImageView(src: item.getPrimaryImage(maxWidth: 120))
|
||||
.frame(width: 120, height: 180)
|
||||
.cornerRadius(10)
|
||||
VStack(alignment: .leading) {
|
||||
|
@ -249,7 +248,7 @@ struct MovieItemView: View {
|
|||
} else {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
ImageView(src: item.getBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 200),
|
||||
ImageView(src: item.getBackdropImage(maxWidth: 200),
|
||||
bh: item.getBackdropImageBlurHash())
|
||||
.opacity(0.3)
|
||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing,
|
||||
|
@ -258,7 +257,7 @@ struct MovieItemView: View {
|
|||
.blur(radius: 4)
|
||||
HStack {
|
||||
VStack {
|
||||
ImageView(src: item.getPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 120),
|
||||
ImageView(src: item.getPrimaryImage(maxWidth: 120),
|
||||
bh: item.getPrimaryImageBlurHash())
|
||||
.frame(width: 120, height: 180)
|
||||
.cornerRadius(10)
|
||||
|
|
|
@ -26,7 +26,7 @@ struct NextUpView: View {
|
|||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
VStack(alignment: .leading) {
|
||||
ImageView(src: item.getSeriesPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 100), bh: item.getSeriesPrimaryImageBlurHash())
|
||||
ImageView(src: item.getSeriesPrimaryImage(maxWidth: 100), bh: item.getSeriesPrimaryImageBlurHash())
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
Spacer().frame(height: 5)
|
||||
|
|
|
@ -50,7 +50,7 @@ struct SeasonItemView: View {
|
|||
if isLoading {
|
||||
EmptyView()
|
||||
} else {
|
||||
ImageView(src: item.getSeriesBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: UIDevice.current.userInterfaceIdiom == .pad ? 622 : Int(UIScreen.main.bounds.width)), bh: item.getSeriesBackdropImageBlurHash())
|
||||
ImageView(src: item.getSeriesBackdropImage(maxWidth: UIDevice.current.userInterfaceIdiom == .pad ? 622 : Int(UIScreen.main.bounds.width)), bh: item.getSeriesBackdropImageBlurHash())
|
||||
.opacity(0.4)
|
||||
.blur(radius: 2.0)
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ struct SeasonItemView: View {
|
|||
|
||||
var portraitHeaderOverlayView: some View {
|
||||
HStack(alignment: .bottom, spacing: 12) {
|
||||
ImageView(src: item.getPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 120), bh: item.getPrimaryImageBlurHash())
|
||||
ImageView(src: item.getPrimaryImage(maxWidth: 120), bh: item.getPrimaryImageBlurHash())
|
||||
.frame(width: 120, height: 180)
|
||||
.cornerRadius(10)
|
||||
VStack(alignment: .leading) {
|
||||
|
@ -97,7 +97,7 @@ struct SeasonItemView: View {
|
|||
ForEach(episodes, id: \.id) { episode in
|
||||
NavigationLink(destination: ItemView(item: episode)) {
|
||||
HStack {
|
||||
ImageView(src: episode.getPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 150), bh: episode.getPrimaryImageBlurHash())
|
||||
ImageView(src: episode.getPrimaryImage(maxWidth: 150), bh: episode.getPrimaryImageBlurHash())
|
||||
.shadow(radius: 5)
|
||||
.frame(width: 150, height: 90)
|
||||
.cornerRadius(10)
|
||||
|
@ -156,7 +156,7 @@ struct SeasonItemView: View {
|
|||
} else {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
ImageView(src: item.getSeriesBackdropImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 200), bh: item.getSeriesBackdropImageBlurHash())
|
||||
ImageView(src: item.getSeriesBackdropImage(maxWidth: 200), bh: item.getSeriesBackdropImageBlurHash())
|
||||
.opacity(0.4)
|
||||
.frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing,
|
||||
height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom)
|
||||
|
@ -165,7 +165,7 @@ struct SeasonItemView: View {
|
|||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Spacer().frame(height: 16)
|
||||
ImageView(src: item.getPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 120), bh: item.getPrimaryImageBlurHash())
|
||||
ImageView(src: item.getPrimaryImage(maxWidth: 120), bh: item.getPrimaryImageBlurHash())
|
||||
.frame(width: 120, height: 180)
|
||||
.cornerRadius(10)
|
||||
Spacer().frame(height: 4)
|
||||
|
@ -190,7 +190,7 @@ struct SeasonItemView: View {
|
|||
ForEach(episodes, id: \.id) { episode in
|
||||
NavigationLink(destination: ItemView(item: episode)) {
|
||||
HStack {
|
||||
ImageView(src: episode.getPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 150), bh: episode.getPrimaryImageBlurHash())
|
||||
ImageView(src: episode.getPrimaryImage(maxWidth: 150), bh: episode.getPrimaryImageBlurHash())
|
||||
.shadow(radius: 5)
|
||||
.frame(width: 150, height: 90)
|
||||
.cornerRadius(10)
|
||||
|
|
|
@ -62,7 +62,7 @@ struct SeriesItemView: View {
|
|||
ForEach(seasons, id: \.id) { season in
|
||||
NavigationLink(destination: ItemView(item: season)) {
|
||||
VStack(alignment: .leading) {
|
||||
ImageView(src: season.getPrimaryImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 100), bh: season.getPrimaryImageBlurHash())
|
||||
ImageView(src: season.getPrimaryImage(maxWidth: 100), bh: season.getPrimaryImageBlurHash())
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: 5)
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
import SwiftUI
|
||||
|
||||
struct SplashView: View {
|
||||
@StateObject
|
||||
var viewModel = SplashViewModel()
|
||||
@StateObject var viewModel = SplashViewModel()
|
||||
|
||||
var body: some View {
|
||||
if viewModel.isLoggedIn {
|
||||
|
|
|
@ -15,28 +15,28 @@ extension BaseItemDto {
|
|||
|
||||
// MARK: Images
|
||||
func getSeriesBackdropImageBlurHash() -> String {
|
||||
let rawImgURL = self.getSeriesBackdropImage(baseURL: "", maxWidth: 1).absoluteString
|
||||
let rawImgURL = self.getSeriesBackdropImage(maxWidth: 1).absoluteString
|
||||
let imgTag = rawImgURL.components(separatedBy: "&tag=")[1]
|
||||
|
||||
return self.imageBlurHashes?.backdrop?[imgTag] ?? "001fC^"
|
||||
}
|
||||
|
||||
func getSeriesPrimaryImageBlurHash() -> String {
|
||||
let rawImgURL = self.getSeriesPrimaryImage(baseURL: "", maxWidth: 1).absoluteString
|
||||
let rawImgURL = self.getSeriesPrimaryImage(maxWidth: 1).absoluteString
|
||||
let imgTag = rawImgURL.components(separatedBy: "&tag=")[1]
|
||||
|
||||
return self.imageBlurHashes?.primary?[imgTag] ?? "001fC^"
|
||||
}
|
||||
|
||||
func getPrimaryImageBlurHash() -> String {
|
||||
let rawImgURL = self.getPrimaryImage(baseURL: "", maxWidth: 1).absoluteString
|
||||
let rawImgURL = self.getPrimaryImage(maxWidth: 1).absoluteString
|
||||
let imgTag = rawImgURL.components(separatedBy: "&tag=")[1]
|
||||
|
||||
return self.imageBlurHashes?.primary?[imgTag] ?? "001fC^"
|
||||
}
|
||||
|
||||
func getBackdropImageBlurHash() -> String {
|
||||
let rawImgURL = self.getBackdropImage(baseURL: "", maxWidth: 1).absoluteString
|
||||
let rawImgURL = self.getBackdropImage(maxWidth: 1).absoluteString
|
||||
let imgTag = rawImgURL.components(separatedBy: "&tag=")[1]
|
||||
|
||||
if rawImgURL.contains("Backdrop") {
|
||||
|
@ -46,7 +46,7 @@ extension BaseItemDto {
|
|||
}
|
||||
}
|
||||
|
||||
func getBackdropImage(baseURL: String, maxWidth: Int) -> URL {
|
||||
func getBackdropImage(maxWidth: Int) -> URL {
|
||||
var imageType = ""
|
||||
var imageTag = ""
|
||||
|
||||
|
@ -68,31 +68,28 @@ extension BaseItemDto {
|
|||
}
|
||||
|
||||
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
|
||||
let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)"
|
||||
let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)"
|
||||
return URL(string: urlString)!
|
||||
}
|
||||
|
||||
func getSeriesBackdropImage(baseURL: String, maxWidth: Int) -> URL {
|
||||
func getSeriesBackdropImage(maxWidth: Int) -> URL {
|
||||
let imageType = "Backdrop"
|
||||
let imageTag = (self.parentBackdropImageTags ?? [""])[0]
|
||||
|
||||
print(imageType)
|
||||
print(imageTag)
|
||||
|
||||
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
|
||||
let urlString = "\(baseURL)/Items/\(self.parentBackdropItemId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)"
|
||||
let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(self.parentBackdropItemId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)"
|
||||
return URL(string: urlString)!
|
||||
}
|
||||
|
||||
func getSeriesPrimaryImage(baseURL: String, maxWidth: Int) -> URL {
|
||||
func getSeriesPrimaryImage(maxWidth: Int) -> URL {
|
||||
let imageType = "Primary"
|
||||
let imageTag = self.seriesPrimaryImageTag ?? ""
|
||||
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
|
||||
let urlString = "\(baseURL)/Items/\(self.seriesId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)"
|
||||
let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(self.seriesId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)"
|
||||
return URL(string: urlString)!
|
||||
}
|
||||
|
||||
func getPrimaryImage(baseURL: String, maxWidth: Int) -> URL {
|
||||
func getPrimaryImage(maxWidth: Int) -> URL {
|
||||
let imageType = "Primary"
|
||||
var imageTag = self.imageTags?["Primary"] ?? ""
|
||||
|
||||
|
@ -101,7 +98,7 @@ extension BaseItemDto {
|
|||
}
|
||||
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
|
||||
|
||||
let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)"
|
||||
let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)"
|
||||
return URL(string: urlString)!
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/* 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
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import JellyfinAPI
|
||||
|
||||
func HandleAPIRequestCompletion(completion: Subscribers.Completion<Error>, vm: ViewModel) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
break
|
||||
case .failure(let error):
|
||||
if let err = error as? ErrorResponse {
|
||||
switch err {
|
||||
case .error(401, _, _, _):
|
||||
vm.errorMessage = err.localizedDescription
|
||||
SessionManager.current.logout()
|
||||
case .error:
|
||||
vm.errorMessage = err.localizedDescription
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
|
@ -109,12 +109,14 @@ final class SessionManager {
|
|||
|
||||
self.user = user
|
||||
generateAuthHeader(with: accessToken)
|
||||
|
||||
print(JellyfinAPI.customHeaders)
|
||||
let nc = NotificationCenter.default
|
||||
nc.post(name: Notification.Name("didSignIn"), object: nil)
|
||||
}
|
||||
|
||||
func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> {
|
||||
generateAuthHeader(with: nil)
|
||||
|
||||
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
|
||||
.map { response -> (SignedInUser, String?) in
|
||||
let user = SignedInUser(context: PersistenceController.shared.container.viewContext)
|
||||
|
@ -147,7 +149,7 @@ final class SessionManager {
|
|||
func logout() {
|
||||
let keychain = KeychainSwift()
|
||||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||
keychain.delete("AccessToken_\(user.user_id ?? "")")
|
||||
keychain.delete("AccessToken_\(user?.user_id ?? "")")
|
||||
generateAuthHeader(with: nil)
|
||||
|
||||
let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID])
|
||||
|
|
|
@ -37,7 +37,7 @@ final class ConnectToServerViewModel: ViewModel {
|
|||
if ServerEnvironment.current.server != nil {
|
||||
UserAPI.getPublicUsers()
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(completion: completion, vm: self)
|
||||
self.HandleAPIRequestCompletion(completion: completion)
|
||||
}, receiveValue: { response in
|
||||
self.publicUsers = response
|
||||
self.isConnectedServer = true
|
||||
|
@ -74,7 +74,7 @@ final class ConnectToServerViewModel: ViewModel {
|
|||
func login() {
|
||||
SessionManager.current.login(username: username, password: password)
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(completion: completion, vm: self)
|
||||
self.HandleAPIRequestCompletion(completion: completion)
|
||||
}, receiveValue: { _ in
|
||||
|
||||
})
|
||||
|
|
|
@ -33,24 +33,29 @@ final class HomeViewModel: ViewModel {
|
|||
}
|
||||
|
||||
func refresh() {
|
||||
UserAPI.getCurrentUser()
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { completion in
|
||||
print(completion)
|
||||
}, receiveValue: { response in
|
||||
let libraries = response.configuration?.orderedViews ?? []
|
||||
self.librariesShowRecentlyAddedIDs = libraries.filter { element in
|
||||
!(response.configuration?.latestItemsExcludes?.contains(element))!
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
|
||||
UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { completion in
|
||||
print(completion)
|
||||
self.HandleAPIRequestCompletion(completion: completion)
|
||||
}, receiveValue: { response in
|
||||
self.libraries = response.items ?? []
|
||||
response.items!.forEach { item in
|
||||
if(item.collectionType == "movies" || item.collectionType == "tvshows") {
|
||||
self.libraries.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
UserAPI.getCurrentUser()
|
||||
.trackActivity(self.loading)
|
||||
.sink(receiveCompletion: { completion in
|
||||
self.HandleAPIRequestCompletion(completion: completion)
|
||||
}, receiveValue: { response in
|
||||
self.libraries.forEach { library in
|
||||
if(!(response.configuration?.latestItemsExcludes?.contains(library.id!))!) {
|
||||
self.librariesShowRecentlyAddedIDs.append(library.id!)
|
||||
}
|
||||
}
|
||||
})
|
||||
.store(in: &self.cancellables)
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
@ -59,7 +64,7 @@ final class HomeViewModel: ViewModel {
|
|||
mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { completion in
|
||||
print(completion)
|
||||
self.HandleAPIRequestCompletion(completion: completion)
|
||||
}, receiveValue: { response in
|
||||
self.resumeItems = response.items ?? []
|
||||
})
|
||||
|
@ -68,8 +73,8 @@ final class HomeViewModel: ViewModel {
|
|||
TvShowsAPI.getNextUp(userId: SessionManager.current.user.user_id!, limit: 12,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { result in
|
||||
print(result)
|
||||
.sink(receiveCompletion: { completion in
|
||||
self.HandleAPIRequestCompletion(completion: completion)
|
||||
}, receiveValue: { response in
|
||||
self.nextUpItems = response.items ?? []
|
||||
})
|
||||
|
|
|
@ -29,7 +29,7 @@ final class LibraryListViewModel: ViewModel {
|
|||
UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { completion in
|
||||
print(completion)
|
||||
self.HandleAPIRequestCompletion(completion: completion)
|
||||
}, receiveValue: { response in
|
||||
self.libraries.append(contentsOf: response.items ?? [])
|
||||
})
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
import ActivityIndicator
|
||||
import JellyfinAPI
|
||||
|
||||
typealias ErrorMessage = String
|
||||
|
||||
|
@ -30,4 +31,22 @@ class ViewModel: ObservableObject {
|
|||
init() {
|
||||
loading.loading.assign(to: \.isLoading, on: self).store(in: &cancellables)
|
||||
}
|
||||
|
||||
func HandleAPIRequestCompletion(completion: Subscribers.Completion<Error>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
break
|
||||
case .failure(let error):
|
||||
if let err = error as? ErrorResponse {
|
||||
switch err {
|
||||
case .error(401, _, _, _):
|
||||
self.errorMessage = err.localizedDescription
|
||||
SessionManager.current.logout()
|
||||
case .error:
|
||||
self.errorMessage = err.localizedDescription
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ struct NextUpWidgetProvider: TimelineProvider {
|
|||
var downloadedItems = [(BaseItemDto, UIImage?)]()
|
||||
items.enumerated().forEach { _, item in
|
||||
dispatchGroup.enter()
|
||||
ImagePipeline.shared.loadImage(with: item.getBackdropImage(baseURL: server.baseURI ?? "", maxWidth: 320)) { result in
|
||||
ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in
|
||||
guard case let .success(image) = result else {
|
||||
dispatchGroup.leave()
|
||||
return
|
||||
|
@ -89,7 +89,7 @@ struct NextUpWidgetProvider: TimelineProvider {
|
|||
var downloadedItems = [(BaseItemDto, UIImage?)]()
|
||||
items.enumerated().forEach { _, item in
|
||||
dispatchGroup.enter()
|
||||
ImagePipeline.shared.loadImage(with: item.getBackdropImage(baseURL: server.baseURI ?? "", maxWidth: 320)) { result in
|
||||
ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in
|
||||
guard case let .success(image) = result else {
|
||||
dispatchGroup.leave()
|
||||
return
|
||||
|
|
Loading…
Reference in New Issue