[create-pull-request] automated change

This commit is contained in:
acvigue 2021-06-18 02:34:18 +00:00 committed by GitHub
parent ac26ac8077
commit add5908be2
23 changed files with 137 additions and 140 deletions

View File

@ -10,19 +10,19 @@
import SwiftUI import SwiftUI
import JellyfinAPI import JellyfinAPI
fileprivate struct CutOffShadow: Shape { private struct CutOffShadow: Shape {
let radius = 6.0; let radius = 6.0
func path(in rect: CGRect) -> Path { func path(in rect: CGRect) -> Path {
var path = Path() var path = Path()
let tl = CGPoint(x: rect.minX, y: rect.minY) let tl = CGPoint(x: rect.minX, y: rect.minY)
let tr = CGPoint(x: rect.maxX, y: rect.minY) let tr = CGPoint(x: rect.maxX, y: rect.minY)
let brs = CGPoint(x: rect.maxX, y: rect.maxY - radius) let brs = CGPoint(x: rect.maxX, y: rect.maxY - radius)
let brc = CGPoint(x: rect.maxX - radius, 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 bls = CGPoint(x: rect.minX + radius, y: rect.maxY)
let blc = CGPoint(x: rect.minX + radius, y: rect.maxY - radius) let blc = CGPoint(x: rect.minX + radius, y: rect.maxY - radius)
path.move(to: tl) path.move(to: tl)
path.addLine(to: tr) path.addLine(to: tr)
path.addLine(to: brs) path.addLine(to: brs)
@ -31,29 +31,29 @@ fileprivate struct CutOffShadow: Shape {
path.addLine(to: bls) path.addLine(to: bls)
path.addRelativeArc(center: blc, radius: radius, path.addRelativeArc(center: blc, radius: radius,
startAngle: Angle.degrees(90), delta: Angle.degrees(90)) startAngle: Angle.degrees(90), delta: Angle.degrees(90))
return path return path
} }
} }
struct LandscapeItemElement: View { struct LandscapeItemElement: View {
@Environment(\.isFocused) var envFocused: Bool @Environment(\.isFocused) var envFocused: Bool
@State var focused: Bool = false; @State var focused: Bool = false
@State var backgroundURL: URL?; @State var backgroundURL: URL?
var item: BaseItemDto; var item: BaseItemDto
var body: some View { var body: some View {
VStack() { VStack {
ImageView(src: (item.type == "Episode" ? item.getSeriesBackdropImage(maxWidth: 445) : item.getBackdropImage(maxWidth: 445)), bh: item.type == "Episode" ? item.getSeriesBackdropImageBlurHash() : item.getBackdropImageBlurHash()) 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) .frame(width: 445, height: 250)
.cornerRadius(10) .cornerRadius(10)
.overlay( .overlay(
Group { Group {
if(focused && item.userData?.playedPercentage != nil) { if focused && item.userData?.playedPercentage != nil {
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
Rectangle() Rectangle()
.fill(LinearGradient(colors: [.black,.clear], startPoint: .bottom, endPoint: .top)) .fill(LinearGradient(colors: [.black, .clear], startPoint: .bottom, endPoint: .top))
.frame(width: 445, height: 90) .frame(width: 445, height: 90)
.mask(CutOffShadow()) .mask(CutOffShadow())
VStack(alignment: .leading) { VStack(alignment: .leading) {
@ -79,7 +79,7 @@ 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)
.shadow(radius: focused ? 10.0 : 0, y: focused ? 10.0 : 0) .shadow(radius: focused ? 10.0 : 0, y: focused ? 10.0 : 0)
if(focused) { if focused {
Text(item.type == "Episode" ? "\(item.seriesName ?? "") • S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0))" : item.name ?? "") Text(item.type == "Episode" ? "\(item.seriesName ?? "") • S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0))" : item.name ?? "")
.font(.callout) .font(.callout)
.fontWeight(.semibold) .fontWeight(.semibold)
@ -93,11 +93,11 @@ struct LandscapeItemElement: View {
withAnimation(.linear(duration: 0.15)) { withAnimation(.linear(duration: 0.15)) {
self.focused = envFocus self.focused = envFocus
} }
if(envFocus == true) { if envFocus == true {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// your code here // your code here
if(self.focused == true) { if self.focused == true {
backgroundURL = item.getBackdropImage(maxWidth: 1080) backgroundURL = item.getBackdropImage(maxWidth: 1080)
BackgroundManager.current.setBackground(to: backgroundURL!, hash: item.getBackdropImageBlurHash()) BackgroundManager.current.setBackground(to: backgroundURL!, hash: item.getBackdropImageBlurHash())
} }

View File

@ -10,19 +10,19 @@
import SwiftUI import SwiftUI
import JellyfinAPI import JellyfinAPI
fileprivate struct CutOffShadow: Shape { private struct CutOffShadow: Shape {
let radius = 6.0; let radius = 6.0
func path(in rect: CGRect) -> Path { func path(in rect: CGRect) -> Path {
var path = Path() var path = Path()
let tl = CGPoint(x: rect.minX, y: rect.minY) let tl = CGPoint(x: rect.minX, y: rect.minY)
let tr = CGPoint(x: rect.maxX, y: rect.minY) let tr = CGPoint(x: rect.maxX, y: rect.minY)
let brs = CGPoint(x: rect.maxX, y: rect.maxY - radius) let brs = CGPoint(x: rect.maxX, y: rect.maxY - radius)
let brc = CGPoint(x: rect.maxX - radius, 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 bls = CGPoint(x: rect.minX + radius, y: rect.maxY)
let blc = CGPoint(x: rect.minX + radius, y: rect.maxY - radius) let blc = CGPoint(x: rect.minX + radius, y: rect.maxY - radius)
path.move(to: tl) path.move(to: tl)
path.addLine(to: tr) path.addLine(to: tr)
path.addLine(to: brs) path.addLine(to: brs)
@ -31,20 +31,20 @@ fileprivate struct CutOffShadow: Shape {
path.addLine(to: bls) path.addLine(to: bls)
path.addRelativeArc(center: blc, radius: radius, path.addRelativeArc(center: blc, radius: radius,
startAngle: Angle.degrees(90), delta: Angle.degrees(90)) startAngle: Angle.degrees(90), delta: Angle.degrees(90))
return path return path
} }
} }
struct PortraitItemElement: View { struct PortraitItemElement: View {
@Environment(\.isFocused) var envFocused: Bool @Environment(\.isFocused) var envFocused: Bool
@State var focused: Bool = false; @State var focused: Bool = false
@State var backgroundURL: URL?; @State var backgroundURL: URL?
var item: BaseItemDto; var item: BaseItemDto
var body: some View { var body: some View {
VStack() { VStack {
ImageView(src: item.type == "Episode" ? item.getSeriesPrimaryImage(maxWidth: 200) : item.getPrimaryImage(maxWidth: 200), bh: item.type == "Episode" ? item.getSeriesPrimaryImageBlurHash() : item.getPrimaryImageBlurHash()) 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) .frame(width: 200, height: 300)
.cornerRadius(10) .cornerRadius(10)
@ -55,11 +55,11 @@ struct PortraitItemElement: View {
withAnimation(.linear(duration: 0.15)) { withAnimation(.linear(duration: 0.15)) {
self.focused = envFocus self.focused = envFocus
} }
if(envFocus == true) { if envFocus == true {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// your code here // your code here
if(self.focused == true) { if self.focused == true {
backgroundURL = item.getBackdropImage(maxWidth: 1080) backgroundURL = item.getBackdropImage(maxWidth: 1080)
BackgroundManager.current.setBackground(to: backgroundURL!, hash: item.getBackdropImageBlurHash()) BackgroundManager.current.setBackground(to: backgroundURL!, hash: item.getBackdropImageBlurHash())
} }

View File

@ -13,9 +13,9 @@ import CoreMedia
struct PublicUserButton: View { struct PublicUserButton: View {
@Environment(\.isFocused) var envFocused: Bool @Environment(\.isFocused) var envFocused: Bool
@State var focused: Bool = false; @State var focused: Bool = false
var publicUser: UserDto var publicUser: UserDto
var body: some View { var body: some View {
VStack { VStack {
if publicUser.primaryImageTag != nil { if publicUser.primaryImageTag != nil {
@ -31,7 +31,7 @@ struct PublicUserButton: View {
.cornerRadius(125.0) .cornerRadius(125.0)
.shadow(radius: 6) .shadow(radius: 6)
} }
if(focused) { if focused {
Text(publicUser.name ?? "").font(.headline).fontWeight(.semibold) Text(publicUser.name ?? "").font(.headline).fontWeight(.semibold)
} else { } else {
Spacer().frame(height: 60) Spacer().frame(height: 60)

View File

@ -16,12 +16,12 @@ struct ConnectToServerView: View {
if viewModel.isConnectedServer { if viewModel.isConnectedServer {
if viewModel.publicUsers.isEmpty { if viewModel.publicUsers.isEmpty {
Section(header: Text(viewModel.lastPublicUsers.isEmpty || viewModel.username == "" ? "Login to \(ServerEnvironment.current.server.name ?? "")": "")) { Section(header: Text(viewModel.lastPublicUsers.isEmpty || viewModel.username == "" ? "Login to \(ServerEnvironment.current.server.name ?? "")": "")) {
if(viewModel.lastPublicUsers.isEmpty || viewModel.username == "") { if viewModel.lastPublicUsers.isEmpty || viewModel.username == "" {
TextField("Username", text: $viewModel.username) TextField("Username", text: $viewModel.username)
.disableAutocorrection(true) .disableAutocorrection(true)
.autocapitalization(.none) .autocapitalization(.none)
} else { } else {
HStack() { HStack {
Spacer() Spacer()
ImageView(src: URL(string: "\(ServerEnvironment.current.server.baseURI ?? "")/Users/\(viewModel.selectedPublicUser.id ?? "")/Images/Primary?width=500&quality=80&tag=\(viewModel.selectedPublicUser.primaryImageTag ?? "")")!) ImageView(src: URL(string: "\(ServerEnvironment.current.server.baseURI ?? "")/Users/\(viewModel.selectedPublicUser.id ?? "")/Images/Primary?width=500&quality=80&tag=\(viewModel.selectedPublicUser.primaryImageTag ?? "")")!)
.frame(width: 250, height: 250) .frame(width: 250, height: 250)
@ -29,16 +29,16 @@ struct ConnectToServerView: View {
Spacer() Spacer()
} }
} }
SecureField("Password (optional)", text: $viewModel.password) SecureField("Password (optional)", text: $viewModel.password)
.disableAutocorrection(true) .disableAutocorrection(true)
.autocapitalization(.none) .autocapitalization(.none)
} }
Section { Section {
HStack() { HStack {
Button { Button {
if(!viewModel.lastPublicUsers.isEmpty) { if !viewModel.lastPublicUsers.isEmpty {
viewModel.username = "" viewModel.username = ""
viewModel.showPublicUsers() viewModel.showPublicUsers()
} else { } else {
@ -51,7 +51,7 @@ struct ConnectToServerView: View {
} }
Spacer() Spacer()
} }
Button { Button {
viewModel.login() viewModel.login()
} label: { } label: {
@ -66,11 +66,11 @@ struct ConnectToServerView: View {
} }
} }
} else { } else {
VStack() { VStack {
HStack() { HStack {
ForEach(viewModel.publicUsers, id: \.id) { publicUser in ForEach(viewModel.publicUsers, id: \.id) { publicUser in
Button(action: { Button(action: {
if(SessionManager.current.doesUserHaveSavedSession(userID: publicUser.id!)) { if SessionManager.current.doesUserHaveSavedSession(userID: publicUser.id!) {
let user = SessionManager.current.getSavedSession(userID: publicUser.id!) let user = SessionManager.current.getSavedSession(userID: publicUser.id!)
SessionManager.current.loginWithSavedSession(user: user) SessionManager.current.loginWithSavedSession(user: user)
} else { } else {
@ -87,7 +87,7 @@ struct ConnectToServerView: View {
}.buttonStyle(PlainNavigationLinkButtonStyle()) }.buttonStyle(PlainNavigationLinkButtonStyle())
} }
}.padding(.bottom, 20) }.padding(.bottom, 20)
HStack() { HStack {
Spacer() Spacer()
Button { Button {
viewModel.hidePublicUsers() viewModel.hidePublicUsers()

View File

@ -12,7 +12,7 @@ import SwiftUI
struct HomeView: View { struct HomeView: View {
@StateObject var viewModel = HomeViewModel() @StateObject var viewModel = HomeViewModel()
@State var showingSettings = false @State var showingSettings = false
var body: some View { var body: some View {
@ -32,9 +32,9 @@ struct HomeView: View {
ForEach(viewModel.librariesShowRecentlyAddedIDs, id: \.self) { libraryID in ForEach(viewModel.librariesShowRecentlyAddedIDs, id: \.self) { libraryID in
VStack(alignment: .leading) { VStack(alignment: .leading) {
let library = viewModel.libraries.first(where: { $0.id == libraryID }) let library = viewModel.libraries.first(where: { $0.id == libraryID })
NavigationLink(destination: Text("library_latest")) { NavigationLink(destination: Text("library_latest")) {
HStack() { HStack {
Text("Latest \(library?.name ?? "")") Text("Latest \(library?.name ?? "")")
.font(.headline) .font(.headline)
.fontWeight(.semibold) .fontWeight(.semibold)

View File

@ -11,7 +11,7 @@ import UIKit
@main @main
struct JellyfinPlayer_tvOSApp: App { struct JellyfinPlayer_tvOSApp: App {
let persistenceController = PersistenceController.shared let persistenceController = PersistenceController.shared
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
SplashView() SplashView()

View File

@ -15,16 +15,16 @@ struct MainTabView: View {
@StateObject private var viewModel = MainTabViewModel() @StateObject private var viewModel = MainTabViewModel()
@State private var backdropAnim: Bool = true @State private var backdropAnim: Bool = true
@State private var lastBackdropAnim: Bool = false @State private var lastBackdropAnim: Bool = false
var body: some View { var body: some View {
ZStack() { ZStack {
//please do not touch my magical crossfading. i will wave my magical github wand and cry // please do not touch my magical crossfading. i will wave my magical github wand and cry
if(viewModel.lastBackgroundURL != nil) { if viewModel.lastBackgroundURL != nil {
ImageView(src: viewModel.lastBackgroundURL!, bh: viewModel.backgroundBlurHash) ImageView(src: viewModel.lastBackgroundURL!, bh: viewModel.backgroundBlurHash)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity) .frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
.opacity(lastBackdropAnim ? 0.4 : 0) .opacity(lastBackdropAnim ? 0.4 : 0)
} }
if(viewModel.backgroundURL != nil) { if viewModel.backgroundURL != nil {
ImageView(src: viewModel.backgroundURL!, bh: viewModel.backgroundBlurHash) ImageView(src: viewModel.backgroundURL!, bh: viewModel.backgroundBlurHash)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity) .frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
.opacity(backdropAnim ? 0.4 : 0) .opacity(backdropAnim ? 0.4 : 0)
@ -37,7 +37,7 @@ struct MainTabView: View {
} }
} }
} }
TabView(selection: $tabSelection) { TabView(selection: $tabSelection) {
HomeView() HomeView()
.offset(y: -20) .offset(y: -20)
@ -46,7 +46,7 @@ struct MainTabView: View {
Image(systemName: "house") Image(systemName: "house")
} }
.tag(Tab.home) .tag(Tab.home)
Text("Library") Text("Library")
.tabItem { .tabItem {
Text(Tab.allMedia.localized) Text(Tab.allMedia.localized)
@ -62,7 +62,7 @@ extension MainTabView {
enum Tab: String { enum Tab: String {
case home case home
case allMedia case allMedia
var localized: String { var localized: String {
switch self { switch self {
case .home: case .home:

View File

@ -11,11 +11,11 @@ import SwiftUI
struct SplashView: View { struct SplashView: View {
@StateObject var viewModel = SplashViewModel() @StateObject var viewModel = SplashViewModel()
var body: some View { var body: some View {
Group { Group {
if viewModel.isLoggedIn { if viewModel.isLoggedIn {
NavigationView() { NavigationView {
MainTabView() MainTabView()
}.padding(.all, -1) }.padding(.all, -1)
} else { } else {

View File

@ -12,7 +12,7 @@ import SwiftUI
struct MainTabView: View { struct MainTabView: View {
@State private var tabSelection: Tab = .home @State private var tabSelection: Tab = .home
var body: some View { var body: some View {
TabView(selection: $tabSelection) { TabView(selection: $tabSelection) {
NavigationView { NavigationView {
@ -38,11 +38,11 @@ struct MainTabView: View {
} }
extension MainTabView { extension MainTabView {
enum Tab: String { enum Tab: String {
case home case home
case allMedia case allMedia
var localized: String { var localized: String {
switch self { switch self {
case .home: case .home:

View File

@ -18,7 +18,6 @@ struct SeasonItemView: View {
var item: BaseItemDto = BaseItemDto() var item: BaseItemDto = BaseItemDto()
@State private var episodes: [BaseItemDto] = [] @State private var episodes: [BaseItemDto] = []
@State private var isLoading: Bool = true @State private var isLoading: Bool = true
@State private var viewDidLoad: Bool = false @State private var viewDidLoad: Bool = false
@ -249,7 +248,7 @@ struct SeasonItemView: View {
} }
} }
} }
var body: some View { var body: some View {
if isLoading { if isLoading {
ProgressView() ProgressView()

View File

@ -26,7 +26,6 @@ struct SeriesItemView: View {
} }
isLoading = true isLoading = true
DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.global(qos: .userInitiated).async {
TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])

View File

@ -10,16 +10,16 @@ import SwiftUI
struct SettingsView: View { struct SettingsView: View {
@Environment(\.managedObjectContext) private var viewContext @Environment(\.managedObjectContext) private var viewContext
@ObservedObject var viewModel: SettingsViewModel @ObservedObject var viewModel: SettingsViewModel
@Binding var close: Bool @Binding var close: Bool
@State private var inNetworkStreamBitrate: Int = 40_000_000 @State private var inNetworkStreamBitrate: Int = 40_000_000
@State private var outOfNetworkStreamBitrate: Int = 40_000_000 @State private var outOfNetworkStreamBitrate: Int = 40_000_000
@State private var autoSelectSubtitles: Bool = false @State private var autoSelectSubtitles: Bool = false
@State private var autoSelectSubtitlesLangcode: String = "none" @State private var autoSelectSubtitlesLangcode: String = "none"
@State private var username: String = "" @State private var username: String = ""
func onAppear() { func onAppear() {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
username = SessionManager.current.user.username! username = SessionManager.current.user.username!
@ -28,7 +28,7 @@ struct SettingsView: View {
autoSelectSubtitles = defaults.bool(forKey: "AutoSelectSubtitles") autoSelectSubtitles = defaults.bool(forKey: "AutoSelectSubtitles")
autoSelectSubtitlesLangcode = defaults.string(forKey: "AutoSelectSubtitlesLangcode") ?? "" autoSelectSubtitlesLangcode = defaults.string(forKey: "AutoSelectSubtitlesLangcode") ?? ""
} }
var body: some View { var body: some View {
NavigationView { NavigationView {
Form { Form {
@ -41,7 +41,7 @@ struct SettingsView: View {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
defaults.setValue(_inNetworkStreamBitrate.wrappedValue, forKey: "InNetworkBandwidth") defaults.setValue(_inNetworkStreamBitrate.wrappedValue, forKey: "InNetworkBandwidth")
} }
Picker("Default remote quality", selection: $outOfNetworkStreamBitrate) { Picker("Default remote quality", selection: $outOfNetworkStreamBitrate) {
ForEach(self.viewModel.bitrates, id: \.self) { bitrate in ForEach(self.viewModel.bitrates, id: \.self) { bitrate in
Text(bitrate.name).tag(bitrate.value) Text(bitrate.name).tag(bitrate.value)
@ -51,7 +51,7 @@ struct SettingsView: View {
defaults.setValue(_outOfNetworkStreamBitrate.wrappedValue, forKey: "OutOfNetworkBandwidth") defaults.setValue(_outOfNetworkStreamBitrate.wrappedValue, forKey: "OutOfNetworkBandwidth")
} }
} }
Section(header: Text("Accessibility")) { Section(header: Text("Accessibility")) {
Toggle("Automatically show subtitles", isOn: $autoSelectSubtitles).onChange(of: autoSelectSubtitles, perform: { _ in Toggle("Automatically show subtitles", isOn: $autoSelectSubtitles).onChange(of: autoSelectSubtitles, perform: { _ in
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
@ -59,7 +59,7 @@ struct SettingsView: View {
}) })
Picker("Language preferences", selection: $autoSelectSubtitlesLangcode) {} Picker("Language preferences", selection: $autoSelectSubtitlesLangcode) {}
} }
Section { Section {
HStack { HStack {
Text("Signed in as \(username)").foregroundColor(.primary) Text("Signed in as \(username)").foregroundColor(.primary)
@ -67,22 +67,22 @@ struct SettingsView: View {
Button { Button {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server") let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do { do {
try viewContext.execute(deleteRequest) try viewContext.execute(deleteRequest)
} catch _ as NSError { } catch _ as NSError {
// TODO: handle the error // TODO: handle the error
} }
let fetchRequest2: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "SignedInUser") let fetchRequest2: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "SignedInUser")
let deleteRequest2 = NSBatchDeleteRequest(fetchRequest: fetchRequest2) let deleteRequest2 = NSBatchDeleteRequest(fetchRequest: fetchRequest2)
do { do {
try viewContext.execute(deleteRequest2) try viewContext.execute(deleteRequest2)
} catch _ as NSError { } catch _ as NSError {
// TODO: handle the error // TODO: handle the error
} }
SessionManager.current.logout() SessionManager.current.logout()
ServerEnvironment.current.reset() ServerEnvironment.current.reset()
} label: { } label: {

View File

@ -300,7 +300,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
}, receiveValue: { [self] response in }, receiveValue: { [self] response in
playSessionId = response.playSessionId ?? "" playSessionId = response.playSessionId ?? ""
if(response.mediaSources == nil) { if response.mediaSources == nil {
delegate?.exitPlayer(self) delegate?.exitPlayer(self)
return return
} }

View File

@ -38,7 +38,7 @@ extension BaseItemDto {
func getBackdropImageBlurHash() -> String { func getBackdropImageBlurHash() -> String {
let rawImgURL = self.getBackdropImage(maxWidth: 1).absoluteString let rawImgURL = self.getBackdropImage(maxWidth: 1).absoluteString
let imgTag = rawImgURL.components(separatedBy: "&tag=")[1] let imgTag = rawImgURL.components(separatedBy: "&tag=")[1]
if rawImgURL.contains("Backdrop") { if rawImgURL.contains("Backdrop") {
return self.imageBlurHashes?.backdrop?[imgTag] ?? "001fC^" return self.imageBlurHashes?.backdrop?[imgTag] ?? "001fC^"
} else { } else {

View File

@ -21,17 +21,16 @@ final class BackgroundManager {
func setBackground(to: URL, hash: String) { func setBackground(to: URL, hash: String) {
self.backgroundURL = to self.backgroundURL = to
self.blurhash = hash self.blurhash = hash
let nc = NotificationCenter.default let nc = NotificationCenter.default
nc.post(name: Notification.Name("backgroundDidChange"), object: nil) nc.post(name: Notification.Name("backgroundDidChange"), object: nil)
} }
func clearBackground() { func clearBackground() {
self.backgroundURL = nil self.backgroundURL = nil
self.blurhash = "001fC^" self.blurhash = "001fC^"
let nc = NotificationCenter.default let nc = NotificationCenter.default
nc.post(name: Notification.Name("backgroundDidChange"), object: nil) nc.post(name: Notification.Name("backgroundDidChange"), object: nil)
} }
} }

View File

@ -19,8 +19,8 @@ final class ServerEnvironment {
init() { init() {
let serverRequest = Server.fetchRequest() let serverRequest = Server.fetchRequest()
let servers = try? PersistenceController.shared.container.viewContext.fetch(serverRequest) let servers = try? PersistenceController.shared.container.viewContext.fetch(serverRequest)
if(servers?.count != 0) { if servers?.count != 0 {
server = servers?.first server = servers?.first
JellyfinAPI.basePath = server.baseURI! JellyfinAPI.basePath = server.baseURI!
} }
@ -34,7 +34,7 @@ final class ServerEnvironment {
if uri.last == "/" { if uri.last == "/" {
uri = String(uri.dropLast()) uri = String(uri.dropLast())
} }
JellyfinAPI.basePath = uri JellyfinAPI.basePath = uri
return SystemAPI.getPublicSystemInfo() return SystemAPI.getPublicSystemInfo()
.map { response in .map { response in
@ -56,8 +56,8 @@ final class ServerEnvironment {
let serverRequest: NSFetchRequest<NSFetchRequestResult> = Server.fetchRequest() let serverRequest: NSFetchRequest<NSFetchRequestResult> = Server.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: serverRequest) let deleteRequest = NSBatchDeleteRequest(fetchRequest: serverRequest)
//coredata will theoretically never throw // coredata will theoretically never throw
_ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest) _ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest)
} }
} }

View File

@ -27,23 +27,23 @@ final class SessionManager {
#if os(tvOS) #if os(tvOS)
let tvUserManager = TVUserManager() let tvUserManager = TVUserManager()
#endif #endif
init() { init() {
let savedUserRequest = SignedInUser.fetchRequest() let savedUserRequest = SignedInUser.fetchRequest()
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest) let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
#if os(tvOS) #if os(tvOS)
savedUsers?.forEach() { savedUser in savedUsers?.forEach { savedUser in
if(savedUser.appletv_id == tvUserManager.currentUserIdentifier ?? "") { if savedUser.appletv_id == tvUserManager.currentUserIdentifier ?? "" {
self.user = savedUser self.user = savedUser
} }
} }
#else #else
user = savedUsers?.first user = savedUsers?.first
#endif #endif
if(user != nil) { if user != nil {
let authToken = getAuthToken(userID: user.user_id!) let authToken = getAuthToken(userID: user.user_id!)
generateAuthHeader(with: authToken) generateAuthHeader(with: authToken)
} }
@ -70,43 +70,43 @@ final class SessionManager {
deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(user?.user_id ?? "")" deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(user?.user_id ?? "")"
#endif #endif
header.append("Version=\"\(appVersion ?? "0.0.1")\", ") header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
if(authToken != nil) { if authToken != nil {
header.append("Token=\"\(authToken!)\"") header.append("Token=\"\(authToken!)\"")
accessToken = authToken! accessToken = authToken!
} }
JellyfinAPI.customHeaders["X-Emby-Authorization"] = header JellyfinAPI.customHeaders["X-Emby-Authorization"] = header
} }
fileprivate func getAuthToken(userID: String) -> String? { fileprivate func getAuthToken(userID: String) -> String? {
let keychain = KeychainSwift() let keychain = KeychainSwift()
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain" keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
return keychain.get("AccessToken_\(userID)") return keychain.get("AccessToken_\(userID)")
} }
func doesUserHaveSavedSession(userID: String) -> Bool { func doesUserHaveSavedSession(userID: String) -> Bool {
let savedUserRequest = SignedInUser.fetchRequest() let savedUserRequest = SignedInUser.fetchRequest()
savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID) savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest) let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
if(savedUsers!.isEmpty) { if savedUsers!.isEmpty {
return false return false
} }
return true return true
} }
func getSavedSession(userID: String) -> SignedInUser { func getSavedSession(userID: String) -> SignedInUser {
let savedUserRequest = SignedInUser.fetchRequest() let savedUserRequest = SignedInUser.fetchRequest()
savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID) savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest) let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
return savedUsers!.first! return savedUsers!.first!
} }
func loginWithSavedSession(user: SignedInUser) { func loginWithSavedSession(user: SignedInUser) {
let accessToken = getAuthToken(userID: user.user_id!) let accessToken = getAuthToken(userID: user.user_id!)
self.user = user self.user = user
generateAuthHeader(with: accessToken) generateAuthHeader(with: accessToken)
print(JellyfinAPI.customHeaders) print(JellyfinAPI.customHeaders)
@ -116,29 +116,29 @@ final class SessionManager {
func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> { func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> {
generateAuthHeader(with: nil) generateAuthHeader(with: nil)
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password)) return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
.map { response -> (SignedInUser, String?) in .map { response -> (SignedInUser, String?) in
let user = SignedInUser(context: PersistenceController.shared.container.viewContext) let user = SignedInUser(context: PersistenceController.shared.container.viewContext)
user.username = response.user?.name user.username = response.user?.name
user.user_id = response.user?.id user.user_id = response.user?.id
#if os(tvOS) #if os(tvOS)
//user.appletv_id = tvUserManager.currentUserIdentifier ?? "" // user.appletv_id = tvUserManager.currentUserIdentifier ?? ""
#endif #endif
return (user, response.accessToken) return (user, response.accessToken)
} }
.handleEvents(receiveOutput: { [unowned self] response, accessToken in .handleEvents(receiveOutput: { [unowned self] response, accessToken in
user = response user = response
_ = try? PersistenceController.shared.container.viewContext.save() _ = try? PersistenceController.shared.container.viewContext.save()
let keychain = KeychainSwift() let keychain = KeychainSwift()
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain" keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
keychain.set(accessToken!, forKey: "AccessToken_\(user.user_id!)") keychain.set(accessToken!, forKey: "AccessToken_\(user.user_id!)")
generateAuthHeader(with: accessToken) generateAuthHeader(with: accessToken)
let nc = NotificationCenter.default let nc = NotificationCenter.default
nc.post(name: Notification.Name("didSignIn"), object: nil) nc.post(name: Notification.Name("didSignIn"), object: nil)
}) })
@ -155,7 +155,7 @@ final class SessionManager {
let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID]) let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID])
user = nil user = nil
_ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest) _ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest)
let nc = NotificationCenter.default let nc = NotificationCenter.default
nc.post(name: Notification.Name("didSignOut"), object: nil) nc.post(name: Notification.Name("didSignOut"), object: nil)
} }

View File

@ -20,7 +20,7 @@ final class ConnectToServerViewModel: ViewModel {
var username = "" var username = ""
@Published @Published
var password = "" var password = ""
@Published @Published
var lastPublicUsers = [UserDto]() var lastPublicUsers = [UserDto]()
@Published @Published
@ -45,17 +45,17 @@ final class ConnectToServerViewModel: ViewModel {
.store(in: &cancellables) .store(in: &cancellables)
} }
} }
func hidePublicUsers() { func hidePublicUsers() {
self.lastPublicUsers = publicUsers; self.lastPublicUsers = publicUsers
publicUsers = []; publicUsers = []
} }
func showPublicUsers() { func showPublicUsers() {
self.publicUsers = lastPublicUsers; self.publicUsers = lastPublicUsers
lastPublicUsers = []; lastPublicUsers = []
} }
func connectToServer() { func connectToServer() {
ServerEnvironment.current.create(with: uri) ServerEnvironment.current.create(with: uri)
.sink(receiveCompletion: { result in .sink(receiveCompletion: { result in
@ -65,7 +65,7 @@ final class ConnectToServerViewModel: ViewModel {
default: default:
break break
} }
}, receiveValue: { response in }, receiveValue: { _ in
self.getPublicUsers() self.getPublicUsers()
}) })
.store(in: &cancellables) .store(in: &cancellables)
@ -76,7 +76,7 @@ final class ConnectToServerViewModel: ViewModel {
.sink(receiveCompletion: { completion in .sink(receiveCompletion: { completion in
self.HandleAPIRequestCompletion(completion: completion) self.HandleAPIRequestCompletion(completion: completion)
}, receiveValue: { _ in }, receiveValue: { _ in
}) })
.store(in: &cancellables) .store(in: &cancellables)
} }

View File

@ -22,7 +22,7 @@ final class HomeViewModel: ViewModel {
var resumeItems = [BaseItemDto]() var resumeItems = [BaseItemDto]()
@Published @Published
var nextUpItems = [BaseItemDto]() var nextUpItems = [BaseItemDto]()
// temp // temp
var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: ["DateCreated"]) var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: ["DateCreated"])
@ -39,18 +39,18 @@ final class HomeViewModel: ViewModel {
self.HandleAPIRequestCompletion(completion: completion) self.HandleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in }, receiveValue: { response in
response.items!.forEach { item in response.items!.forEach { item in
if(item.collectionType == "movies" || item.collectionType == "tvshows") { if item.collectionType == "movies" || item.collectionType == "tvshows" {
self.libraries.append(item) self.libraries.append(item)
} }
} }
UserAPI.getCurrentUser() UserAPI.getCurrentUser()
.trackActivity(self.loading) .trackActivity(self.loading)
.sink(receiveCompletion: { completion in .sink(receiveCompletion: { completion in
self.HandleAPIRequestCompletion(completion: completion) self.HandleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in }, receiveValue: { response in
self.libraries.forEach { library in self.libraries.forEach { library in
if(!(response.configuration?.latestItemsExcludes?.contains(library.id!))!) { if !(response.configuration?.latestItemsExcludes?.contains(library.id!))! {
self.librariesShowRecentlyAddedIDs.append(library.id!) self.librariesShowRecentlyAddedIDs.append(library.id!)
} }
} }

View File

@ -21,7 +21,7 @@ final class MainTabViewModel: ViewModel {
let nc = NotificationCenter.default let nc = NotificationCenter.default
nc.addObserver(self, selector: #selector(backgroundDidChange), name: Notification.Name("backgroundDidChange"), object: nil) nc.addObserver(self, selector: #selector(backgroundDidChange), name: Notification.Name("backgroundDidChange"), object: nil)
} }
@objc func backgroundDidChange() { @objc func backgroundDidChange() {
self.lastBackgroundURL = self.backgroundURL self.lastBackgroundURL = self.backgroundURL
self.backgroundURL = BackgroundManager.current.backgroundURL self.backgroundURL = BackgroundManager.current.backgroundURL

View File

@ -16,20 +16,20 @@ import WidgetKit
#endif #endif
final class SplashViewModel: ViewModel { final class SplashViewModel: ViewModel {
@Published var isLoggedIn: Bool = false @Published var isLoggedIn: Bool = false
override init() { override init() {
isLoggedIn = ServerEnvironment.current.server != nil && SessionManager.current.user != nil isLoggedIn = ServerEnvironment.current.server != nil && SessionManager.current.user != nil
super.init() super.init()
ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory
DataLoader.sharedUrlCache.diskCapacity = 1000 * 1024 * 1024 // 1000MB disk DataLoader.sharedUrlCache.diskCapacity = 1000 * 1024 * 1024 // 1000MB disk
#if !os(tvOS) #if !os(tvOS)
WidgetCenter.shared.reloadAllTimelines() WidgetCenter.shared.reloadAllTimelines()
#endif #endif
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
if defaults.integer(forKey: "InNetworkBandwidth") == 0 { if defaults.integer(forKey: "InNetworkBandwidth") == 0 {
defaults.setValue(40_000_000, forKey: "InNetworkBandwidth") defaults.setValue(40_000_000, forKey: "InNetworkBandwidth")
@ -37,17 +37,17 @@ final class SplashViewModel: ViewModel {
if defaults.integer(forKey: "OutOfNetworkBandwidth") == 0 { if defaults.integer(forKey: "OutOfNetworkBandwidth") == 0 {
defaults.setValue(40_000_000, forKey: "OutOfNetworkBandwidth") defaults.setValue(40_000_000, forKey: "OutOfNetworkBandwidth")
} }
let nc = NotificationCenter.default let nc = NotificationCenter.default
nc.addObserver(self, selector: #selector(didLogIn), name: Notification.Name("didSignIn"), object: nil) nc.addObserver(self, selector: #selector(didLogIn), name: Notification.Name("didSignIn"), object: nil)
nc.addObserver(self, selector: #selector(didLogOut), name: Notification.Name("didSignOut"), object: nil) nc.addObserver(self, selector: #selector(didLogOut), name: Notification.Name("didSignOut"), object: nil)
} }
@objc func didLogIn() { @objc func didLogIn() {
print("didLogIn") print("didLogIn")
isLoggedIn = true isLoggedIn = true
} }
@objc func didLogOut() { @objc func didLogOut() {
print("didLogOut") print("didLogOut")
isLoggedIn = false isLoggedIn = false

View File

@ -27,11 +27,11 @@ class ViewModel: ObservableObject {
let loading = ActivityIndicator() let loading = ActivityIndicator()
@Published @Published
var errorMessage: ErrorMessage? var errorMessage: ErrorMessage?
init() { init() {
loading.loading.assign(to: \.isLoading, on: self).store(in: &cancellables) loading.loading.assign(to: \.isLoading, on: self).store(in: &cancellables)
} }
func HandleAPIRequestCompletion(completion: Subscribers.Completion<Error>) { func HandleAPIRequestCompletion(completion: Subscribers.Completion<Error>) {
switch completion { switch completion {
case .finished: case .finished:

View File

@ -28,7 +28,7 @@ struct NextUpWidgetProvider: TimelineProvider {
let server = ServerEnvironment.current.server! let server = ServerEnvironment.current.server!
let savedUser = SessionManager.current.user! let savedUser = SessionManager.current.user!
var tempCancellables = Set<AnyCancellable>() var tempCancellables = Set<AnyCancellable>()
JellyfinAPI.basePath = server.baseURI ?? "" JellyfinAPI.basePath = server.baseURI ?? ""
TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3, TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
@ -69,7 +69,7 @@ struct NextUpWidgetProvider: TimelineProvider {
let entryDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)! let entryDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)!
let server = ServerEnvironment.current.server! let server = ServerEnvironment.current.server!
let savedUser = SessionManager.current.user! let savedUser = SessionManager.current.user!
var tempCancellables = Set<AnyCancellable>() var tempCancellables = Set<AnyCancellable>()
JellyfinAPI.basePath = server.baseURI ?? "" JellyfinAPI.basePath = server.baseURI ?? ""
TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3, TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3,