From b26f81247a1472f4310182e2c7a2e5c6b13f22ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 10 Jun 2021 09:28:21 -0700 Subject: [PATCH] [create-pull-request] automated change (#47) Co-authored-by: acvigue --- JellyfinPlayer tvOS/Persistence.swift | 2 +- JellyfinPlayer/ConnectToServerView.swift | 162 +++---- JellyfinPlayer/ContentView.swift | 40 +- JellyfinPlayer/ContinueWatchingView.swift | 23 +- JellyfinPlayer/DeviceProfileBuilder.swift | 102 ++--- JellyfinPlayer/EpisodeItemView.swift | 6 +- JellyfinPlayer/ItemView.swift | 28 +- JellyfinPlayer/JellyfinPlayerApp.swift | 41 +- JellyfinPlayer/LatestMediaView.swift | 32 +- JellyfinPlayer/LibraryListView.swift | 14 +- JellyfinPlayer/LibrarySearchView.swift | 20 +- JellyfinPlayer/LibraryView.swift | 56 +-- JellyfinPlayer/LoadingView.swift | 17 +- JellyfinPlayer/MovieItemView.swift | 4 +- JellyfinPlayer/NextUpView.swift | 26 +- JellyfinPlayer/PersistenceController.swift | 5 +- JellyfinPlayer/SearchBarView.swift | 12 +- JellyfinPlayer/SeasonItemView.swift | 12 +- JellyfinPlayer/SeriesItemView.swift | 39 +- JellyfinPlayer/SettingsView.swift | 4 +- JellyfinPlayer/VideoPlayer.swift | 430 +++++++++--------- JellyfinPlayer/VideoPlayerSettingsView.swift | 20 +- Shared/Extensions/APIExtensions.swift | 118 ++--- Shared/Extensions/BlurHashDecode.swift | 6 +- Shared/Extensions/CollectionExtensions.swift | 1 - .../HandleAPIRequestCompletion.swift | 8 +- Shared/Extensions/ParallaxHeader.swift | 3 +- Shared/Extensions/StringExtensions.swift | 3 +- Shared/Typings/Typings.swift | 10 +- Shared/ViewModel/SettingsViewModel.swift | 12 +- WidgetExtension/NextUpWidget.swift | 12 +- WidgetExtension/WidgetEnvironment.swift | 8 +- 32 files changed, 631 insertions(+), 645 deletions(-) diff --git a/JellyfinPlayer tvOS/Persistence.swift b/JellyfinPlayer tvOS/Persistence.swift index e3ed50ab..13833d5e 100644 --- a/JellyfinPlayer tvOS/Persistence.swift +++ b/JellyfinPlayer tvOS/Persistence.swift @@ -35,7 +35,7 @@ struct PersistenceController { if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") } - container.loadPersistentStores(completionHandler: { (storeDescription, error) in + container.loadPersistentStores(completionHandler: { (_, error) in if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. diff --git a/JellyfinPlayer/ConnectToServerView.swift b/JellyfinPlayer/ConnectToServerView.swift index b8f718e2..5f2473e5 100644 --- a/JellyfinPlayer/ConnectToServerView.swift +++ b/JellyfinPlayer/ConnectToServerView.swift @@ -15,83 +15,83 @@ struct ConnectToServerView: View { @Environment(\.managedObjectContext) private var viewContext @EnvironmentObject var globalData: GlobalData @EnvironmentObject var jsi: justSignedIn - - @State private var uri = ""; - @State private var isWorking = false; - @State private var isErrored = false; - @State private var isDone = false; - @State private var isSignInErrored = false; - @State private var isConnected = false; - @State private var serverName = ""; - @State private var usernameDisabled: Bool = false; - @State private var publicUsers: [UserDto] = []; - @State private var lastPublicUsers: [UserDto] = []; - @State private var username = ""; - @State private var password = ""; - @State private var server_id = ""; - @State private var serverSkipped: Bool = false; - @State private var serverSkippedAlert: Bool = false; - @State private var skip_server_bool: Bool = false; - @State private var skip_server_obj: Server!; - + + @State private var uri = "" + @State private var isWorking = false + @State private var isErrored = false + @State private var isDone = false + @State private var isSignInErrored = false + @State private var isConnected = false + @State private var serverName = "" + @State private var usernameDisabled: Bool = false + @State private var publicUsers: [UserDto] = [] + @State private var lastPublicUsers: [UserDto] = [] + @State private var username = "" + @State private var password = "" + @State private var server_id = "" + @State private var serverSkipped: Bool = false + @State private var serverSkippedAlert: Bool = false + @State private var skip_server_bool: Bool = false + @State private var skip_server_obj: Server! + @Binding var rootIsActive: Bool - - private var reauthDeviceID: String = ""; - private let userUUID = UUID(); - + + private var reauthDeviceID: String = "" + private let userUUID = UUID() + init(skip_server: Bool, skip_server_prefill: Server, reauth_deviceId: String, isActive: Binding) { _rootIsActive = isActive skip_server_bool = skip_server skip_server_obj = skip_server_prefill reauthDeviceID = reauth_deviceId } - + init(isActive: Binding) { _rootIsActive = isActive } - + func start() { - if(skip_server_bool) { + if skip_server_bool { uri = skip_server_obj.baseURI! - + UserAPI.getPublicUsers() .sink(receiveCompletion: { completion in switch completion { case .finished: break - case .failure(_): - skip_server_bool = false; - skip_server_obj = Server(); + case .failure: + skip_server_bool = false + skip_server_obj = Server() break } }, receiveValue: { response in publicUsers = response - - serverSkipped = true; - serverSkippedAlert = true; + + serverSkipped = true + serverSkippedAlert = true server_id = skip_server_obj.server_id! serverName = skip_server_obj.name! - isConnected = true; + isConnected = true }) .store(in: &globalData.pendingAPIRequests) } } - + func doLogin() { isWorking = true - - let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String; - var deviceName = UIDevice.current.name; + + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + var deviceName = UIDevice.current.name deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current) - deviceName = deviceName.removeRegexMatches(pattern: "[^\\w\\s]"); - - let authHeader = "MediaBrowser Client=\"SwiftFin\", Device=\"\(deviceName)\", DeviceId=\"\(serverSkipped ? reauthDeviceID : userUUID.uuidString)\", Version=\"\(appVersion ?? "0.0.1")\""; + deviceName = deviceName.removeRegexMatches(pattern: "[^\\w\\s]") + + let authHeader = "MediaBrowser Client=\"SwiftFin\", Device=\"\(deviceName)\", DeviceId=\"\(serverSkipped ? reauthDeviceID : userUUID.uuidString)\", Version=\"\(appVersion ?? "0.0.1")\"" print(authHeader) - + JellyfinAPI.customHeaders["X-Emby-Authorization"] = authHeader - + let x: AuthenticateUserByName = AuthenticateUserByName(username: username, pw: password, password: nil) - + UserAPI.authenticateUserByName(authenticateUserByName: x) .sink(receiveCompletion: { completion in isWorking = false @@ -106,29 +106,29 @@ struct ConnectToServerView: View { } catch _ as NSError { } - + let fetchRequest2: NSFetchRequest = NSFetchRequest(entityName: "SignedInUser") let deleteRequest2 = NSBatchDeleteRequest(fetchRequest: fetchRequest2) do { try viewContext.execute(deleteRequest2) } catch _ as NSError { - + } - + let newServer = Server(context: viewContext) newServer.baseURI = uri newServer.name = serverName newServer.server_id = server_id - + let newUser = SignedInUser(context: viewContext) newUser.device_uuid = userUUID.uuidString newUser.username = username newUser.user_id = response.user!.id! - + let keychain = KeychainSwift() keychain.set(response.accessToken!, forKey: "AccessToken_\(newUser.user_id!)") - + do { try viewContext.save() DispatchQueue.main.async { [self] in @@ -144,30 +144,30 @@ struct ConnectToServerView: View { }) .store(in: &globalData.pendingAPIRequests) } - + var body: some View { Form { - if(!isConnected) { + if !isConnected { Section(header: Text("Server Information")) { TextField("Jellyfin Server URL", text: $uri) .disableAutocorrection(true) .autocapitalization(.none) Button { - isWorking = true; - if(!uri.contains("http")) { - uri = "http://" + uri; + isWorking = true + if !uri.contains("http") { + uri = "http://" + uri } - if(uri.last == "/") { + if uri.last == "/" { uri = String(uri.dropLast()) } - + JellyfinAPI.basePath = uri SystemAPI.getPublicSystemInfo() .sink(receiveCompletion: { completion in switch completion { case .finished: break - case .failure(_): + case .failure: isErrored = true isWorking = false break @@ -176,15 +176,15 @@ struct ConnectToServerView: View { let server = response serverName = server.serverName! server_id = server.id! - if(server.startupWizardCompleted!) { - isConnected = true; - + if server.startupWizardCompleted! { + isConnected = true + UserAPI.getPublicUsers() .sink(receiveCompletion: { completion in switch completion { case .finished: break - case .failure(_): + case .failure: isErrored = true isWorking = false break @@ -201,7 +201,7 @@ struct ConnectToServerView: View { HStack { Text("Connect") Spacer() - if(isWorking == true) { + if isWorking == true { ProgressView() } } @@ -210,7 +210,7 @@ struct ConnectToServerView: View { Alert(title: Text("Error"), message: Text("Couldn't connect to server"), dismissButton: .default(Text("Try again"))) } } else { - if(publicUsers.count == 0) { + if publicUsers.count == 0 { Section(header: Text("\(serverSkipped ? "Reauthenticate" : "Login") to \(serverName)")) { TextField("Username", text: $username) .disableAutocorrection(true) @@ -225,7 +225,7 @@ struct ConnectToServerView: View { HStack { Text("Login") Spacer() - if(isWorking) { + if isWorking { ProgressView() } } @@ -234,9 +234,9 @@ struct ConnectToServerView: View { Alert(title: Text("Error"), message: Text("Invalid credentials"), dismissButton: .default(Text("Back"))) } } - - if(serverSkipped) { - Section() { + + if serverSkipped { + Section { Button { serverSkippedAlert = false server_id = "" @@ -244,8 +244,8 @@ struct ConnectToServerView: View { isConnected = false serverSkipped = false } label: { - HStack() { - HStack() { + HStack { + HStack { Image(systemName: "chevron.left") Text("Change Server") } @@ -254,13 +254,13 @@ struct ConnectToServerView: View { } } } else { - Section() { + Section { Button { publicUsers = lastPublicUsers - usernameDisabled = false; + usernameDisabled = false } label: { - HStack() { - HStack() { + HStack { + HStack { Image(systemName: "chevron.left") Text("Back") } @@ -272,9 +272,9 @@ struct ConnectToServerView: View { } else { Section(header: Text("\(serverSkipped ? "Reauthenticate" : "Login") to \(serverName)")) { ForEach(publicUsers, id: \.id) { publicUser in - HStack() { + HStack { Button() { - if(publicUser.hasPassword!) { + if publicUser.hasPassword! { lastPublicUsers = publicUsers username = publicUser.name! usernameDisabled = true @@ -286,10 +286,10 @@ struct ConnectToServerView: View { doLogin() } } label: { - HStack() { + HStack { Text(publicUser.name!).font(.subheadline).fontWeight(.semibold) Spacer() - if(publicUser.primaryImageTag != "") { + if publicUser.primaryImageTag != "" { LazyImage(source: URL(string: "\(uri)/Users/\(publicUser.id!)/Images/Primary?width=200&quality=80&tag=\(publicUser.primaryImageTag!)")) .contentMode(.aspectFill) .frame(width: 60, height: 60) @@ -309,14 +309,14 @@ struct ConnectToServerView: View { } } } - - Section() { + + Section { Button() { lastPublicUsers = publicUsers publicUsers = [] username = "" } label: { - HStack() { + HStack { Text("Other User").font(.subheadline).fontWeight(.semibold) Spacer() Image(systemName: "person.fill.questionmark") diff --git a/JellyfinPlayer/ContentView.swift b/JellyfinPlayer/ContentView.swift index 5cd96842..2397805f 100644 --- a/JellyfinPlayer/ContentView.swift +++ b/JellyfinPlayer/ContentView.swift @@ -17,7 +17,7 @@ struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext @EnvironmentObject var orientationInfo: OrientationInfo @EnvironmentObject var jsi: justSignedIn - + @StateObject private var globalData = GlobalData() @FetchRequest(entity: Server.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Server.name, ascending: true)]) @@ -36,16 +36,16 @@ struct ContentView: View { @State private var showSettingsPopover: Bool = false @State private var viewDidLoad: Bool = false @State private var loadState: Int = 2 - + private var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: ["DateCreated"]) func startup() { - if(viewDidLoad == true) { + if viewDidLoad == true { return } - + viewDidLoad = true - + let size = UIScreen.main.bounds.size if size.width < size.height { orientationInfo.orientation = .portrait @@ -72,21 +72,21 @@ struct ContentView: View { } let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - var deviceName = UIDevice.current.name; + var deviceName = UIDevice.current.name deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current) - deviceName = deviceName.removeRegexMatches(pattern: "[^\\w\\s]"); - + deviceName = deviceName.removeRegexMatches(pattern: "[^\\w\\s]") + var header = "MediaBrowser " header.append("Client=\"SwiftFin\", ") header.append("Device=\"\(deviceName)\", ") header.append("DeviceId=\"\(globalData.user.device_uuid ?? "")\", ") header.append("Version=\"\(appVersion ?? "0.0.1")\", ") header.append("Token=\"\(globalData.authToken)\"") - + globalData.authHeader = header JellyfinAPI.basePath = globalData.server.baseURI ?? "" JellyfinAPI.customHeaders = ["X-Emby-Authorization": globalData.authHeader] - + DispatchQueue.global(qos: .userInitiated).async { UserAPI.getCurrentUser() .sink(receiveCompletion: { completion in @@ -97,13 +97,13 @@ struct ContentView: View { librariesShowRecentlyAdded = libraries.filter { element in return !(response.configuration?.latestItemsExcludes?.contains(element))! } - - if(loadState == 1) { + + if loadState == 1 { isLoading = false } }) .store(in: &globalData.pendingAPIRequests) - + UserViewsAPI.getUserViews(userId: globalData.user.user_id ?? "") .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) @@ -112,14 +112,14 @@ struct ContentView: View { response.items?.forEach({ item in library_names[item.id ?? ""] = item.name }) - - if(loadState == 1) { + + if loadState == 1 { isLoading = false } }) .store(in: &globalData.pendingAPIRequests) } - + let defaults = UserDefaults.standard if defaults.integer(forKey: "InNetworkBandwidth") == 0 { defaults.setValue(40_000_000, forKey: "InNetworkBandwidth") @@ -132,13 +132,13 @@ struct ContentView: View { } var body: some View { - if (needsToSelectServer == true) { + if needsToSelectServer == true { NavigationView { ConnectToServerView(isActive: $needsToSelectServer) } .navigationViewStyle(StackNavigationViewStyle()) .environmentObject(globalData) - } else if (globalData.expiredCredentials == true) { + } else if globalData.expiredCredentials == true { NavigationView { ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user.device_uuid ?? "", isActive: $globalData.expiredCredentials) @@ -148,8 +148,8 @@ struct ContentView: View { } else { if !jsi.did { LoadingView(isShowing: $isLoading) { - VStack() { - if(loadState == 0) { + VStack { + if loadState == 0 { TabView(selection: $tabSelection) { NavigationView { VStack(alignment: .leading) { diff --git a/JellyfinPlayer/ContinueWatchingView.swift b/JellyfinPlayer/ContinueWatchingView.swift index e2c91a11..fe57b654 100644 --- a/JellyfinPlayer/ContinueWatchingView.swift +++ b/JellyfinPlayer/ContinueWatchingView.swift @@ -12,33 +12,32 @@ import JellyfinAPI struct ProgressBar: Shape { 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 br = CGPoint(x: rect.maxX, y: rect.maxY) let bls = CGPoint(x: rect.minX + 10, y: rect.maxY) let blc = CGPoint(x: rect.minX + 10, y: rect.maxY - 10) - + path.move(to: tl) path.addLine(to: tr) path.addLine(to: br) path.addLine(to: bls) path.addRelativeArc(center: blc, radius: 10, startAngle: Angle.degrees(90), delta: Angle.degrees(90)) - + return path } } - struct ContinueWatchingView: View { @EnvironmentObject var globalData: GlobalData - + @State private var items: [BaseItemDto] = [] - + func onAppear() { DispatchQueue.global(qos: .userInitiated).async { - ItemsAPI.getResumeItems(userId: globalData.user.user_id ?? "", limit: 12, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary,.backdrop,.thumb]) + ItemsAPI.getResumeItems(userId: globalData.user.user_id ?? "", limit: 12, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb]) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) }, receiveValue: { response in @@ -47,12 +46,12 @@ struct ContinueWatchingView: View { .store(in: &globalData.pendingAPIRequests) } } - + var body: some View { ScrollView(.horizontal, showsIndicators: false) { - if(items.count > 0) { - LazyHStack() { - Spacer().frame(width:14) + if items.count > 0 { + LazyHStack { + Spacer().frame(width: 14) ForEach(items, id: \.id) { item in NavigationLink(destination: ItemView(item: item)) { VStack(alignment: .leading) { @@ -70,7 +69,7 @@ struct ContinueWatchingView: View { .cornerRadius(10) .overlay( Group { - if(item.type == "Episode") { + if item.type == "Episode" { Text("\(item.name!)") .font(.caption) .padding(6) diff --git a/JellyfinPlayer/DeviceProfileBuilder.swift b/JellyfinPlayer/DeviceProfileBuilder.swift index c09839bc..c702b55a 100644 --- a/JellyfinPlayer/DeviceProfileBuilder.swift +++ b/JellyfinPlayer/DeviceProfileBuilder.swift @@ -5,7 +5,7 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ -//lol can someone buy me a coffee this took forever :| +// lol can someone buy me a coffee this took forever :| import Foundation import JellyfinAPI @@ -34,65 +34,65 @@ enum CPUModel { } class DeviceProfileBuilder { - public var bitrate: Int = 0; - + public var bitrate: Int = 0 + public func setMaxBitrate(bitrate: Int) { self.bitrate = bitrate } - + public func buildProfile() -> DeviceProfile { - let maxStreamingBitrate = bitrate; - let maxStaticBitrate = bitrate; - let musicStreamingTranscodingBitrate = 384000; - - //Build direct play profiles - var directPlayProfiles: [DirectPlayProfile] = []; + let maxStreamingBitrate = bitrate + let maxStaticBitrate = bitrate + let musicStreamingTranscodingBitrate = 384000 + + // Build direct play profiles + var directPlayProfiles: [DirectPlayProfile] = [] directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav", videoCodec: "h264", type: .video)] - - //Device supports Dolby Digital (AC3, EAC3) - if(supportsFeature(minimumSupported: .A8X)) { - if(supportsFeature(minimumSupported: .A10)) { - directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "hevc,h264,hev1", type: .video)] //HEVC/H.264 with Dolby Digital + + // Device supports Dolby Digital (AC3, EAC3) + if supportsFeature(minimumSupported: .A8X) { + if supportsFeature(minimumSupported: .A10) { + directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "hevc,h264,hev1", type: .video)] // HEVC/H.264 with Dolby Digital } else { - directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "ac3,eac3,aac,mp3,wav,opus", videoCodec: "h264", type: .video)] //H.264 with Dolby Digital + directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "ac3,eac3,aac,mp3,wav,opus", videoCodec: "h264", type: .video)] // H.264 with Dolby Digital } } - - //Device supports Dolby Vision? - if(supportsFeature(minimumSupported: .A10X)) { - directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "dvhe,dvh1,dva1,dvav,h264,hevc,hev1", type: .video)] //H.264/HEVC with Dolby Digital - No Atmos - Vision + + // Device supports Dolby Vision? + if supportsFeature(minimumSupported: .A10X) { + directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "dvhe,dvh1,dva1,dvav,h264,hevc,hev1", type: .video)] // H.264/HEVC with Dolby Digital - No Atmos - Vision } - - //Device supports Dolby Atmos? - if(supportsFeature(minimumSupported: .A12)) { - directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,truehd,dts,dca,opus", videoCodec: "h264,hevc,dvhe,dvh1,dva1,dvav,h264,hevc,hev1", type: .video)] //H.264/HEVC with Dolby Digital & Atmos - Vision + + // Device supports Dolby Atmos? + if supportsFeature(minimumSupported: .A12) { + directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,truehd,dts,dca,opus", videoCodec: "h264,hevc,dvhe,dvh1,dva1,dvav,h264,hevc,hev1", type: .video)] // H.264/HEVC with Dolby Digital & Atmos - Vision } - - //Build transcoding profiles - var transcodingProfiles: [TranscodingProfile] = []; + + // Build transcoding profiles + var transcodingProfiles: [TranscodingProfile] = [] transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264", audioCodec: "aac,mp3,wav")] - - //Device supports Dolby Digital (AC3, EAC3) - if(supportsFeature(minimumSupported: .A8X)) { - if(supportsFeature(minimumSupported: .A10)) { + + // Device supports Dolby Digital (AC3, EAC3) + if supportsFeature(minimumSupported: .A8X) { + if supportsFeature(minimumSupported: .A10) { transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "aac,mp3,wav,eac3,ac3,flac,opus", audioCodec: "h264,hevc,hev1", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } else { transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264", audioCodec: "aac,mp3,wav,eac3,ac3,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } } - - //Device supports Dolby Vision? - if(supportsFeature(minimumSupported: .A10X)) { + + // Device supports Dolby Vision? + if supportsFeature(minimumSupported: .A10X) { transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "dva1,dvav,dvhe,dvh1,hevc,h264,hev1", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } - - //Device supports Dolby Atmos? - if(supportsFeature(minimumSupported: .A12)) { + + // Device supports Dolby Atmos? + if supportsFeature(minimumSupported: .A12) { transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "dva1,dvav,dvhe,dvh1,hevc,h264,hev1", audioCodec: "aac,mp3,wav,ac3,eac3,flac,dts,truehd,dca,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } - + var codecProfiles: [CodecProfile] = [] - + let h264CodecConditions: [ProfileCondition] = [ ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false), ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|baseline|constrained baseline", isRequired: false), @@ -103,13 +103,13 @@ class DeviceProfileBuilder { ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "main|main 10", isRequired: false), ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "160", isRequired: false), ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)] - + codecProfiles.append(CodecProfile(type: .video, applyConditions: h264CodecConditions, codec: "h264")) - - if(supportsFeature(minimumSupported: .A10)) { - codecProfiles.append(CodecProfile(type: .video, applyConditions: hevcCodecConditions,codec: "hevc")) + + if supportsFeature(minimumSupported: .A10) { + codecProfiles.append(CodecProfile(type: .video, applyConditions: hevcCodecConditions, codec: "hevc")) } - + var subtitleProfiles: [SubtitleProfile] = [] subtitleProfiles.append(SubtitleProfile(format: "vtt", method: .external)) subtitleProfiles.append(SubtitleProfile(format: "ass", method: .external)) @@ -121,12 +121,12 @@ class DeviceProfileBuilder { subtitleProfiles.append(SubtitleProfile(format: "pgs", method: .embed)) let responseProfiles: [ResponseProfile] = [ResponseProfile(container: "m4v", type: .video, mimeType: "video/mp4")] - + let profile = DeviceProfile(maxStreamingBitrate: maxStreamingBitrate, maxStaticBitrate: maxStaticBitrate, musicStreamingTranscodingBitrate: musicStreamingTranscodingBitrate, directPlayProfiles: directPlayProfiles, transcodingProfiles: transcodingProfiles, containerProfiles: [], codecProfiles: codecProfiles, responseProfiles: responseProfiles, subtitleProfiles: subtitleProfiles) - + return profile } - + private func supportsFeature(minimumSupported: CPUModel) -> Bool { let intValues: [CPUModel: Int] = [.A4: 1, .A5: 2, .A5X: 3, .A6: 4, .A6X: 5, .A7: 6, .A7X: 7, .A8: 8, .A8X: 9, .A9: 10, .A9X: 11, .A10: 12, .A10X: 13, .A11: 14, .A12: 15, .A12X: 16, .A12Z: 16, .A13: 17, .A14: 18, .A99: 99] return intValues[CPUinfo()] ?? 0 >= intValues[minimumSupported] ?? 0 @@ -147,7 +147,7 @@ class DeviceProfileBuilder { uname(&systemInfo) let machineMirror = Mirror(reflecting: systemInfo.machine) let identifier = machineMirror.children.reduce("") { identifier, element in - guard let value = element.value as? Int8 , value != 0 else { return identifier } + guard let value = element.value as? Int8, value != 0 else { return identifier } return identifier + String(UnicodeScalar(UInt8(value))) } #endif @@ -189,10 +189,10 @@ class DeviceProfileBuilder { case "iPad7,1", "iPad7,2": return .A10X case "iPad7,3", "iPad7,4": return .A10X case "iPad7,5", "iPad7,6", "iPad7,11", "iPad7,12": return .A10 - case "iPad8,1", "iPad8,2" ,"iPad8,3", "iPad8,4": return .A12X - case "iPad8,5", "iPad8,6" ,"iPad8,7", "iPad8,8": return .A12X + case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4": return .A12X + case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": return .A12X case "iPad8,9", "iPad8,10", "iPad8,11", "iPad8,12": return .A12Z - case "iPad11,3", "iPad11,4" ,"iPad11,6", "iPad11,7": return .A12 + case "iPad11,3", "iPad11,4", "iPad11,6", "iPad11,7": return .A12 case "iPad13,1", "iPad13,2": return .A14 case "AppleTV5,3": return .A8 case "AppleTV6,2": return .A10X diff --git a/JellyfinPlayer/EpisodeItemView.swift b/JellyfinPlayer/EpisodeItemView.swift index fd1ae110..6a8dbe8b 100644 --- a/JellyfinPlayer/EpisodeItemView.swift +++ b/JellyfinPlayer/EpisodeItemView.swift @@ -13,7 +13,7 @@ struct EpisodeItemView: View { @EnvironmentObject private var globalData: GlobalData @EnvironmentObject private var orientationInfo: OrientationInfo @EnvironmentObject private var playbackInfo: VideoPlayerItem - + var item: BaseItemDto @State private var settingState: Bool = true @@ -200,7 +200,7 @@ struct EpisodeItemView: View { HStack { Spacer().frame(width: 16) ForEach(item.people!, id: \.self) { person in - if(person.type! == "Actor") { + if person.type! == "Actor" { NavigationLink(destination: LazyView { LibraryView(withPerson: person) }) { @@ -399,7 +399,7 @@ struct EpisodeItemView: View { HStack { Spacer().frame(width: 16) ForEach(item.people!, id: \.self) { person in - if(person.type! == "Actor") { + if person.type! == "Actor" { NavigationLink(destination: LazyView { LibraryView(withPerson: person) }) { diff --git a/JellyfinPlayer/ItemView.swift b/JellyfinPlayer/ItemView.swift index 7389f942..01677170 100644 --- a/JellyfinPlayer/ItemView.swift +++ b/JellyfinPlayer/ItemView.swift @@ -10,26 +10,26 @@ import Introspect import JellyfinAPI class VideoPlayerItem: ObservableObject { - @Published var shouldShowPlayer: Bool = false; - @Published var itemToPlay: BaseItemDto = BaseItemDto(); + @Published var shouldShowPlayer: Bool = false + @Published var itemToPlay: BaseItemDto = BaseItemDto() } struct ItemView: View { @EnvironmentObject private var globalData: GlobalData - private var item: BaseItemDto; - + private var item: BaseItemDto + @StateObject private var videoPlayerItem: VideoPlayerItem = VideoPlayerItem() - @State private var videoIsLoading: Bool = false; //This variable is only changed by the underlying VLC view. - @State private var isLoading: Bool = false; - @State private var viewDidLoad: Bool = false; - + @State private var videoIsLoading: Bool = false; // This variable is only changed by the underlying VLC view. + @State private var isLoading: Bool = false + @State private var viewDidLoad: Bool = false + init(item: BaseItemDto) { self.item = item } - + var body: some View { VStack { - if(videoPlayerItem.shouldShowPlayer) { + if videoPlayerItem.shouldShowPlayer { LoadingViewNoBlur(isShowing: $videoIsLoading) { VLCPlayerWithControls(item: videoPlayerItem.itemToPlay, loadBinding: $videoIsLoading, pBinding: _videoPlayerItem.projectedValue.shouldShowPlayer) }.navigationBarHidden(true) @@ -42,13 +42,13 @@ struct ItemView: View { .supportedOrientations(.landscape) } else { VStack { - if(item.type == "Movie") { + if item.type == "Movie" { MovieItemView(item: item) - } else if(item.type == "Season") { + } else if item.type == "Season" { SeasonItemView(item: item) - } else if(item.type == "Series") { + } else if item.type == "Series" { SeriesItemView(item: item) - } else if(item.type == "Episode") { + } else if item.type == "Episode" { EpisodeItemView(item: item) } else { Text("Type: \(item.type ?? "") not implemented yet :(") diff --git a/JellyfinPlayer/JellyfinPlayerApp.swift b/JellyfinPlayer/JellyfinPlayerApp.swift index baf0571d..c85c1033 100644 --- a/JellyfinPlayer/JellyfinPlayerApp.swift +++ b/JellyfinPlayer/JellyfinPlayerApp.swift @@ -19,11 +19,11 @@ class OrientationInfo: ObservableObject { case portrait case landscape } - - @Published var orientation: Orientation = .portrait; - + + @Published var orientation: Orientation = .portrait + private var _observer: NSObjectProtocol? - + init() { _observer = NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: nil) { [weak self] note in guard let device = note.object as? UIDevice else { @@ -31,13 +31,12 @@ class OrientationInfo: ObservableObject { } if device.orientation.isPortrait { self?.orientation = .portrait - } - else if device.orientation.isLandscape { + } else if device.orientation.isLandscape { self?.orientation = .landscape } } } - + deinit { if let observer = _observer { NotificationCenter.default.removeObserver(observer) @@ -52,7 +51,7 @@ extension View { } struct HostingWindowFinder: UIViewRepresentable { - var callback: (UIWindow?) -> () + var callback: (UIWindow?) -> Void func makeUIView(context: Context) -> UIView { let view = UIView() @@ -89,7 +88,7 @@ struct ViewPreferenceKey: PreferenceKey { struct SupportedOrientationsPreferenceKey: PreferenceKey { typealias Value = UIInterfaceOrientationMask static var defaultValue: UIInterfaceOrientationMask = .allButUpsideDown - + static func reduce(value: inout UIInterfaceOrientationMask, nextValue: () -> UIInterfaceOrientationMask) { // use the most restrictive set from the stack value.formIntersection(nextValue()) @@ -129,27 +128,27 @@ class PreferenceUIHostingController: UIHostingController { override var prefersHomeIndicatorAutoHidden: Bool { _prefersHomeIndicatorAutoHidden } - + // MARK: Lock orientation - + public var _orientations: UIInterfaceOrientationMask = .allButUpsideDown { didSet { - UIViewController.attemptRotationToDeviceOrientation(); - if(_orientations == .landscape) { - let value = UIInterfaceOrientation.landscapeRight.rawValue; + UIViewController.attemptRotationToDeviceOrientation() + if _orientations == .landscape { + let value = UIInterfaceOrientation.landscapeRight.rawValue UIDevice.current.setValue(value, forKey: "orientation") } } - }; + } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { _orientations } - + public var _viewPreference: UIUserInterfaceStyle = .unspecified { didSet { overrideUserInterfaceStyle = _viewPreference } - }; + } } extension View { @@ -157,12 +156,12 @@ extension View { func prefersHomeIndicatorAutoHidden(_ value: Bool) -> some View { preference(key: PrefersHomeIndicatorAutoHiddenPreferenceKey.self, value: value) } - + func supportedOrientations(_ supportedOrientations: UIInterfaceOrientationMask) -> some View { // When rendered, export the requested orientations upward to Root preference(key: SupportedOrientationsPreferenceKey.self, value: supportedOrientations) } - + func overrideViewPreference(_ viewPreference: UIUserInterfaceStyle) -> some View { // When rendered, export the requested orientations upward to Root preference(key: ViewPreferenceKey.self, value: viewPreference) @@ -173,14 +172,14 @@ extension View { struct JellyfinPlayerApp: App { let persistenceController = PersistenceController.shared @StateObject private var jsi = justSignedIn() - + var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(OrientationInfo()) .environmentObject(jsi) - .withHostingWindow() { window in + .withHostingWindow { window in window?.rootViewController = PreferenceUIHostingController(wrappedView: ContentView().environment(\.managedObjectContext, persistenceController.container.viewContext).environmentObject(OrientationInfo()).environmentObject(jsi)) } } diff --git a/JellyfinPlayer/LatestMediaView.swift b/JellyfinPlayer/LatestMediaView.swift index 8db70053..d16cbcf9 100644 --- a/JellyfinPlayer/LatestMediaView.swift +++ b/JellyfinPlayer/LatestMediaView.swift @@ -11,23 +11,23 @@ import JellyfinAPI struct LatestMediaView: View { @EnvironmentObject var globalData: GlobalData - + @State var items: [BaseItemDto] = [] - private var library_id: String = ""; - @State private var viewDidLoad: Bool = false; - + private var library_id: String = "" + @State private var viewDidLoad: Bool = false + init(usingParentID: String) { - library_id = usingParentID; + library_id = usingParentID } - + func onAppear() { - if(viewDidLoad == true) { + if viewDidLoad == true { return } - viewDidLoad = true; - + viewDidLoad = true + DispatchQueue.global(qos: .userInitiated).async { - UserLibraryAPI.getLatestMedia(userId: globalData.user.user_id!, parentId: library_id, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], enableUserData: true, limit: 12) + UserLibraryAPI.getLatestMedia(userId: globalData.user.user_id!, parentId: library_id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], enableUserData: true, limit: 12) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) }, receiveValue: { response in @@ -36,16 +36,16 @@ struct LatestMediaView: View { .store(in: &globalData.pendingAPIRequests) } } - + var body: some View { ScrollView(.horizontal, showsIndicators: false) { - LazyHStack() { - Spacer().frame(width:16) + LazyHStack { + Spacer().frame(width: 16) ForEach(items, id: \.id) { item in - if(item.type == "Series" || item.type == "Movie") { + if item.type == "Series" || item.type == "Movie" { NavigationLink(destination: ItemView(item: item)) { VStack(alignment: .leading) { - Spacer().frame(height:10) + Spacer().frame(height: 10) LazyImage(source: item.getSeriesPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100)) .placeholderAndFailure { Image(uiImage: UIImage(blurHash: item.getSeriesPrimaryImageBlurHash(), size: CGSize(width: 16, height: 20))!) @@ -55,7 +55,7 @@ struct LatestMediaView: View { } .frame(width: 100, height: 150) .cornerRadius(10) - Spacer().frame(height:5) + Spacer().frame(height: 5) Text(item.seriesName ?? item.name ?? "") .font(.caption) .fontWeight(.semibold) diff --git a/JellyfinPlayer/LibraryListView.swift b/JellyfinPlayer/LibraryListView.swift index 532185c0..0c69aa54 100644 --- a/JellyfinPlayer/LibraryListView.swift +++ b/JellyfinPlayer/LibraryListView.swift @@ -10,26 +10,26 @@ import SwiftUI struct LibraryListView: View { @EnvironmentObject var globalData: GlobalData - + @State var library_ids: [String] = ["favorites", "genres"] @State var library_names: [String: String] = ["favorites": "Favorites", "genres": "Genres"] - var libraries: [String: String] = [:] //input libraries + var libraries: [String: String] = [:] // input libraries var withFavorites: LibraryFilters = LibraryFilters(filters: [.isFavorite], sortOrder: [.descending], sortBy: ["SortName"]) - + init(libraries: [String: String]) { self.libraries = libraries } - + func onAppear() { - if(library_ids.count == 2) { - libraries.forEach() { k,v in + if library_ids.count == 2 { + libraries.forEach { k, v in print("\(k): \(v)") _library_ids.wrappedValue.append(k) _library_names.wrappedValue[k] = v } } } - + var body: some View { List(library_ids, id: \.self) { key in switch key { diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index 0f7f9404..9e2a9993 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -12,13 +12,13 @@ import JellyfinAPI struct LibrarySearchView: View { @EnvironmentObject var globalData: GlobalData @EnvironmentObject var orientationInfo: OrientationInfo - + @State private var items: [BaseItemDto] = [] @State private var searchQuery: String = "" @State private var isLoading: Bool = false private var usingParentID: String = "" @State private var lastSearchTime: Double = CACurrentMediaTime() - + init(usingParentID: String) { self.usingParentID = usingParentID } @@ -27,12 +27,12 @@ struct LibrarySearchView: View { recalcTracks() requestSearch(query: "") } - + func requestSearch(query: String) { isLoading = true DispatchQueue.global(qos: .userInitiated).async { - ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, limit: 60, recursive: true, searchTerm: query, sortOrder: [.ascending], parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], includeItemTypes: ["Movie","Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true) + ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, limit: 60, recursive: true, searchTerm: query, sortOrder: [.ascending], parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: ["Movie", "Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) }, receiveValue: { response in @@ -42,8 +42,8 @@ struct LibrarySearchView: View { .store(in: &globalData.pendingAPIRequests) } } - - //MARK: tracks for grid + + // MARK: tracks for grid @State private var tracks: [GridItem] = [] func recalcTracks() { let trkCnt = Int(floor(UIScreen.main.bounds.size.width / 125)) @@ -57,12 +57,12 @@ struct LibrarySearchView: View { VStack { Spacer().frame(height: 6) SearchBar(text: $searchQuery) - if(isLoading == true) { + if isLoading == true { Spacer() ProgressView() Spacer() } else { - if(!items.isEmpty) { + if !items.isEmpty { ScrollView(.vertical) { Spacer().frame(height: 16) LazyVGrid(columns: tracks) { @@ -105,7 +105,7 @@ struct LibrarySearchView: View { .onAppear(perform: onAppear) .navigationBarTitle("Search", displayMode: .inline) .onChange(of: searchQuery) { query in - if(CACurrentMediaTime() - lastSearchTime > 0.5) { + if CACurrentMediaTime() - lastSearchTime > 0.5 { lastSearchTime = CACurrentMediaTime() requestSearch(query: query) } @@ -113,4 +113,4 @@ struct LibrarySearchView: View { } } -//stream NM5 by nicki! +// stream NM5 by nicki! diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index df579adf..08812122 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -13,10 +13,10 @@ import JellyfinAPI struct LibraryView: View { @EnvironmentObject var globalData: GlobalData @EnvironmentObject var orientationInfo: OrientationInfo - + @State private var items: [BaseItemDto] = [] @State private var isLoading: Bool = false - + var usingParentID: String = "" var title: String = "" var filters: LibraryFilters = LibraryFilters() @@ -24,52 +24,52 @@ struct LibraryView: View { var genre: String = "" var studio: String = "" - @State private var totalPages: Int = 0; - @State private var currentPage: Int = 0; - @State private var isSearching: String? = ""; - @State private var viewDidLoad: Bool = false; - + @State private var totalPages: Int = 0 + @State private var currentPage: Int = 0 + @State private var isSearching: String? = "" + @State private var viewDidLoad: Bool = false + init(usingParentID: String, title: String) { self.usingParentID = usingParentID self.title = title } - + init(usingParentID: String, title: String, usingFilters: LibraryFilters) { self.usingParentID = usingParentID self.title = title self.filters = usingFilters } - + init(withPerson: BaseItemPerson) { self.usingParentID = "" self.title = withPerson.name ?? "" self.personId = withPerson.id! } - + init(withGenre: NameGuidPair) { self.usingParentID = "" self.title = withGenre.name ?? "" self.genre = withGenre.id ?? "" } - + init(withStudio: NameGuidPair) { self.usingParentID = "" self.title = withStudio.name ?? "" self.studio = withStudio.id ?? "" } - + func onAppear() { recalcTracks() - - if(viewDidLoad) { + + if viewDidLoad { return } - + isLoading = true items = [] - + DispatchQueue.global(qos: .userInitiated).async { - ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], includeItemTypes: ["Movie","Series"], filters: filters.filters, sortBy: filters.sortBy, enableUserData: true, personIds: (personId == "" ? nil : [personId]), studioIds: (studio == "" ? nil : [studio]), genreIds: (genre == "" ? nil : [genre]), enableImages: true) + ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: ["Movie", "Series"], filters: filters.filters, sortBy: filters.sortBy, enableUserData: true, personIds: (personId == "" ? nil : [personId]), studioIds: (studio == "" ? nil : [studio]), genreIds: (genre == "" ? nil : [genre]), enableImages: true) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) isLoading = false @@ -83,8 +83,8 @@ struct LibraryView: View { .store(in: &globalData.pendingAPIRequests) } } - - //MARK: tracks for grid + + // MARK: tracks for grid @State private var tracks: [GridItem] = [] func recalcTracks() { let trkCnt = Int(floor(UIScreen.main.bounds.size.width / 125)) @@ -96,10 +96,10 @@ struct LibraryView: View { var body: some View { ZStack { - if(isLoading == true) { + if isLoading == true { ProgressView() } else { - if(!items.isEmpty) { + if !items.isEmpty { VStack { ScrollView(.vertical) { Spacer().frame(height: 16) @@ -132,10 +132,10 @@ struct LibraryView: View { }.onChange(of: orientationInfo.orientation) { _ in recalcTracks() } - if(totalPages > 1) { - HStack() { + if totalPages > 1 { + HStack { Spacer() - HStack() { + HStack { Button { currentPage = currentPage - 1 onAppear() @@ -169,7 +169,7 @@ struct LibraryView: View { .navigationBarTitle(title, displayMode: .inline) .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { - if(currentPage > 0) { + if currentPage > 0 { Button { currentPage = currentPage - 1 onAppear() @@ -177,7 +177,7 @@ struct LibraryView: View { Image(systemName: "chevron.left") } } - if(currentPage < totalPages - 1) { + if currentPage < totalPages - 1 { Button { currentPage = currentPage + 1 onAppear() @@ -185,7 +185,7 @@ struct LibraryView: View { Image(systemName: "chevron.right") } } - if(usingParentID != "") { + if usingParentID != "" { NavigationLink(destination: LibrarySearchView(usingParentID: usingParentID)) { Image(systemName: "magnifyingglass") } @@ -195,4 +195,4 @@ struct LibraryView: View { } } -//stream BM^S by nicki! +// stream BM^S by nicki! diff --git a/JellyfinPlayer/LoadingView.swift b/JellyfinPlayer/LoadingView.swift index 3f1449c8..015ba61f 100644 --- a/JellyfinPlayer/LoadingView.swift +++ b/JellyfinPlayer/LoadingView.swift @@ -12,15 +12,15 @@ struct LoadingView: View where Content: View { @Binding var isShowing: Bool // should the modal be visible? var content: () -> Content var text: String? // the text to display under the ProgressView - defaults to "Loading..." - + var body: some View { - GeometryReader { geometry in + GeometryReader { _ in ZStack(alignment: .center) { // the content to display - if the modal is showing, we'll blur it content() .disabled(isShowing) .blur(radius: isShowing ? 2 : 0) - + // all contents inside here will only be shown when isShowing is true if isShowing { // this Rectangle is a semi-transparent black overlay @@ -30,7 +30,7 @@ struct LoadingView: View where Content: View { // the magic bit - our ProgressView just displays an activity // indicator, with some text underneath showing what we are doing - HStack() { + HStack { ProgressView() Text(text ?? "Loading").fontWeight(.semibold).font(.callout).offset(x: 60) Spacer() @@ -51,14 +51,14 @@ struct LoadingViewNoBlur: View where Content: View { @Binding var isShowing: Bool // should the modal be visible? var content: () -> Content var text: String? // the text to display under the ProgressView - defaults to "Loading..." - + var body: some View { - GeometryReader { geometry in + GeometryReader { _ in ZStack(alignment: .center) { // the content to display - if the modal is showing, we'll blur it content() .disabled(isShowing) - + // all contents inside here will only be shown when isShowing is true if isShowing { // this Rectangle is a semi-transparent black overlay @@ -68,7 +68,7 @@ struct LoadingViewNoBlur: View where Content: View { // the magic bit - our ProgressView just displays an activity // indicator, with some text underneath showing what we are doing - HStack() { + HStack { ProgressView() Text(text ?? "Loading").fontWeight(.semibold).font(.callout).offset(x: 60) Spacer() @@ -83,4 +83,3 @@ struct LoadingViewNoBlur: View where Content: View { } } } - diff --git a/JellyfinPlayer/MovieItemView.swift b/JellyfinPlayer/MovieItemView.swift index 691c6b7f..103c62a8 100644 --- a/JellyfinPlayer/MovieItemView.swift +++ b/JellyfinPlayer/MovieItemView.swift @@ -200,7 +200,7 @@ struct MovieItemView: View { HStack { Spacer().frame(width: 16) ForEach(item.people!, id: \.self) { person in - if(person.type! == "Actor") { + if person.type! == "Actor" { NavigationLink(destination: LazyView { LibraryView(withPerson: person) }) { @@ -399,7 +399,7 @@ struct MovieItemView: View { HStack { Spacer().frame(width: 16) ForEach(item.people!, id: \.self) { person in - if(person.type! == "Actor") { + if person.type! == "Actor" { NavigationLink(destination: LazyView { LibraryView(withPerson: person) }) { diff --git a/JellyfinPlayer/NextUpView.swift b/JellyfinPlayer/NextUpView.swift index 58477209..78ba4ca6 100644 --- a/JellyfinPlayer/NextUpView.swift +++ b/JellyfinPlayer/NextUpView.swift @@ -11,18 +11,18 @@ import JellyfinAPI struct NextUpView: View { @EnvironmentObject var globalData: GlobalData - + @State private var items: [BaseItemDto] = [] - @State private var viewDidLoad: Bool = false; - + @State private var viewDidLoad: Bool = false + func onAppear() { - if(viewDidLoad == true) { + if viewDidLoad == true { return } - viewDidLoad = true; - + viewDidLoad = true + DispatchQueue.global(qos: .userInitiated).async { - TvShowsAPI.getNextUp(userId: globalData.user.user_id!, limit: 12, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people]) + TvShowsAPI.getNextUp(userId: globalData.user.user_id!, limit: 12, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) }, receiveValue: { response in @@ -31,17 +31,17 @@ struct NextUpView: View { .store(in: &globalData.pendingAPIRequests) } } - + var body: some View { VStack(alignment: .leading) { - if(items.count != 0) { + if items.count != 0 { Text("Next Up") .font(.title2) .fontWeight(.bold) .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) ScrollView(.horizontal, showsIndicators: false) { - LazyHStack() { - Spacer().frame(width:16) + LazyHStack { + Spacer().frame(width: 16) ForEach(items, id: \.id) { item in NavigationLink(destination: ItemView(item: item)) { VStack(alignment: .leading) { @@ -54,7 +54,7 @@ struct NextUpView: View { } .frame(width: 100, height: 150) .cornerRadius(10) - Spacer().frame(height:5) + Spacer().frame(height: 5) Text(item.seriesName!) .font(.caption) .fontWeight(.semibold) @@ -66,7 +66,7 @@ struct NextUpView: View { .foregroundColor(.secondary) .lineLimit(1) }.frame(width: 100) - Spacer().frame(width:16) + Spacer().frame(width: 16) } } } diff --git a/JellyfinPlayer/PersistenceController.swift b/JellyfinPlayer/PersistenceController.swift index ad84aad8..5e7b23ae 100644 --- a/JellyfinPlayer/PersistenceController.swift +++ b/JellyfinPlayer/PersistenceController.swift @@ -14,7 +14,6 @@ struct PersistenceController { let result = PersistenceController(inMemory: true) let viewContext = result.container.viewContext - do { try viewContext.save() } catch { @@ -32,11 +31,11 @@ struct PersistenceController { container = NSPersistentCloudKitContainer(name: "Model") container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: "group.me.vigue.jellyfin.mobileclient")!.appendingPathComponent("\(container.name).sqlite"))] - + if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") } - container.loadPersistentStores(completionHandler: { (storeDescription, error) in + container.loadPersistentStores(completionHandler: { (_, error) in if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. diff --git a/JellyfinPlayer/SearchBarView.swift b/JellyfinPlayer/SearchBarView.swift index 20257cb9..266a7da0 100644 --- a/JellyfinPlayer/SearchBarView.swift +++ b/JellyfinPlayer/SearchBarView.swift @@ -8,15 +8,15 @@ */ import SwiftUI - + struct SearchBar: View { @Binding var text: String - + @State private var isEditing = false - + var body: some View { HStack { - + TextField("Search ...", text: $text) .padding(7) .padding(.horizontal, 25) @@ -26,12 +26,12 @@ struct SearchBar: View { .onTapGesture { self.isEditing = true } - + if isEditing { Button(action: { self.isEditing = false self.text = "" - + }) { Text("Cancel") } diff --git a/JellyfinPlayer/SeasonItemView.swift b/JellyfinPlayer/SeasonItemView.swift index 1af2b636..00e2bec8 100644 --- a/JellyfinPlayer/SeasonItemView.swift +++ b/JellyfinPlayer/SeasonItemView.swift @@ -12,24 +12,24 @@ import JellyfinAPI struct SeasonItemView: View { @EnvironmentObject var globalData: GlobalData @EnvironmentObject var orientationInfo: OrientationInfo - + var item: BaseItemDto = BaseItemDto() @State private var episodes: [BaseItemDto] = [] - + @State private var isLoading: Bool = true @State private var viewDidLoad: Bool = false - + init(item: BaseItemDto) { self.item = item } - + func onAppear() { - if(viewDidLoad) { + if viewDidLoad { return } DispatchQueue.global(qos: .userInitiated).async { - TvShowsAPI.getEpisodes(seriesId: item.seriesId ?? "", userId: globalData.user.user_id!, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], seasonId: item.id ?? "") + TvShowsAPI.getEpisodes(seriesId: item.seriesId ?? "", userId: globalData.user.user_id!, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], seasonId: item.id ?? "") .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) isLoading = false diff --git a/JellyfinPlayer/SeriesItemView.swift b/JellyfinPlayer/SeriesItemView.swift index 335d9bad..25ebf127 100644 --- a/JellyfinPlayer/SeriesItemView.swift +++ b/JellyfinPlayer/SeriesItemView.swift @@ -12,23 +12,23 @@ import JellyfinAPI struct SeriesItemView: View { @EnvironmentObject private var globalData: GlobalData @EnvironmentObject private var orientationInfo: OrientationInfo - - var item: BaseItemDto; - - @State private var seasons: [BaseItemDto] = []; - @State private var isLoading: Bool = true; - @State private var viewDidLoad: Bool = false; - + + var item: BaseItemDto + + @State private var seasons: [BaseItemDto] = [] + @State private var isLoading: Bool = true + @State private var viewDidLoad: Bool = false + func onAppear() { recalcTracks() - if(viewDidLoad) { - return; + if viewDidLoad { + return } - + isLoading = true - + DispatchQueue.global(qos: .userInitiated).async { - TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], isMissing: false) + TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], isMissing: false) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) }, receiveValue: { response in @@ -40,17 +40,16 @@ struct SeriesItemView: View { } } - //MARK: Grid tracks + // MARK: Grid tracks func recalcTracks() { - let trkCnt: Int = Int(floor(UIScreen.main.bounds.size.width / 125)); + let trkCnt: Int = Int(floor(UIScreen.main.bounds.size.width / 125)) tracks = [] - for _ in (0.. 0) { - mediaPlayer.jumpForward(Int32(offset)/1000); + if offset > 0 { + mediaPlayer.jumpForward(Int32(offset)/1000) } else { - mediaPlayer.jumpBackward(Int32(abs(offset))/1000); + mediaPlayer.jumpBackward(Int32(abs(offset))/1000) } sendProgressReport(eventName: "unpause") } - + @IBAction func exitButtonPressed(_ sender: Any) { sendStopReport() mediaPlayer.stop() delegate?.exitPlayer(self) } - + @IBAction func controlViewTapped(_ sender: Any) { videoControlsView.isHidden = true } - + @IBAction func contentViewTapped(_ sender: Any) { videoControlsView.isHidden = false controlsAppearTime = CACurrentMediaTime() } - + @IBAction func jumpBackTapped(_ sender: Any) { - if(paused == false) { + if paused == false { mediaPlayer.jumpBackward(15) } } - + @IBAction func jumpForwardTapped(_ sender: Any) { - if(paused == false) { + if paused == false { mediaPlayer.jumpForward(30) } } - + @IBOutlet weak var mainActionButton: UIButton! @IBAction func mainActionButtonPressed(_ sender: Any) { print(mediaPlayer.state.rawValue) - if(paused) { + if paused { mediaPlayer.play() mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal) - paused = false; + paused = false } else { mediaPlayer.pause() mainActionButton.setImage(UIImage(systemName: "play"), for: .normal) - paused = true; + paused = true } } - + @IBAction func settingsButtonTapped(_ sender: UIButton) { optionsVC = VideoPlayerSettingsView() optionsVC?.delegate = self optionsVC?.modalPresentationStyle = .popover optionsVC?.popoverPresentationController?.sourceView = playerSettingsButton - - + // Present the view controller (in a popover). self.present(optionsVC!, animated: true) { print("popover visible, pause playback") @@ -171,122 +170,121 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal) } } - + func settingsPopoverDismissed() { optionsVC?.dismiss(animated: true, completion: nil) self.mediaPlayer.play() self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal) } - + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .landscape } - + override var shouldAutorotate: Bool { return true } - + func setupNowPlayingCC() { let commandCenter = MPRemoteCommandCenter.shared() - commandCenter.playCommand.isEnabled = true; - commandCenter.pauseCommand.isEnabled = true; - commandCenter.seekForwardCommand.isEnabled = true; - commandCenter.seekBackwardCommand.isEnabled = true; + commandCenter.playCommand.isEnabled = true + commandCenter.pauseCommand.isEnabled = true + commandCenter.seekForwardCommand.isEnabled = true + commandCenter.seekBackwardCommand.isEnabled = true commandCenter.changePlaybackPositionCommand.isEnabled = true // Add handler for Pause Command - commandCenter.pauseCommand.addTarget{ event in + commandCenter.pauseCommand.addTarget { _ in self.mediaPlayer.pause() self.sendProgressReport(eventName: "pause") return .success } - - //Add handler for Play command - commandCenter.playCommand.addTarget{ event in + + // Add handler for Play command + commandCenter.playCommand.addTarget { _ in self.mediaPlayer.play() self.sendProgressReport(eventName: "unpause") return .success } - - //Add handler for FF command - commandCenter.seekForwardCommand.addTarget{ event in + + // Add handler for FF command + commandCenter.seekForwardCommand.addTarget { _ in self.mediaPlayer.jumpForward(30) self.sendProgressReport(eventName: "timeupdate") return .success } - - //Add handler for RW command - commandCenter.seekBackwardCommand.addTarget{ event in + + // Add handler for RW command + commandCenter.seekBackwardCommand.addTarget { _ in self.mediaPlayer.jumpBackward(15) self.sendProgressReport(eventName: "timeupdate") return .success } - - //Scrubber + + // Scrubber commandCenter.changePlaybackPositionCommand.addTarget { [weak self](remoteEvent) -> MPRemoteCommandHandlerStatus in guard let self = self else {return .commandFailed} - + if let event = remoteEvent as? MPChangePlaybackPositionCommandEvent { let targetSeconds = event.positionTime - + let videoPosition = Double(self.mediaPlayer.time.intValue) - let offset = targetSeconds - videoPosition; - if(offset > 0) { - self.mediaPlayer.jumpForward(Int32(offset)/1000); + let offset = targetSeconds - videoPosition + if offset > 0 { + self.mediaPlayer.jumpForward(Int32(offset)/1000) } else { - self.mediaPlayer.jumpBackward(Int32(abs(offset))/1000); + self.mediaPlayer.jumpBackward(Int32(abs(offset))/1000) } self.sendProgressReport(eventName: "unpause") - + return .success } else { return .commandFailed } } - - var nowPlayingInfo = [String : Any]() + + var nowPlayingInfo = [String: Any]() nowPlayingInfo[MPMediaItemPropertyTitle] = manifest.name ?? "" MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo - + UIApplication.shared.beginReceivingRemoteControlEvents() } - + override func remoteControlReceived(with event: UIEvent?) { dump(event) } - + override func viewDidLoad() { super.viewDidLoad() - //View has loaded. - - //Rotate to landscape only if necessary - UIViewController.attemptRotationToDeviceOrientation(); - + // View has loaded. + + // Rotate to landscape only if necessary + UIViewController.attemptRotationToDeviceOrientation() + mediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14) - //mediaPlayer.wrappedValue.perform(Selector(("setTextRendererFont:")), with: "Copperplate") - - + // mediaPlayer.wrappedValue.perform(Selector(("setTextRendererFont:")), with: "Copperplate") + mediaPlayer.delegate = self mediaPlayer.drawable = videoContentView - - if(manifest.type == "Movie") { + + if manifest.type == "Movie" { titleLabel.text = manifest.name } else { titleLabel.text = "S\(String(manifest.parentIndexNumber!)):E\(String(manifest.indexNumber!)) “\(manifest.name!)”" } - - //Fetch max bitrate from UserDefaults depending on current connection mode + + // Fetch max bitrate from UserDefaults depending on current connection mode let defaults = UserDefaults.standard let maxBitrate = globalData.isInNetwork ? defaults.integer(forKey: "InNetworkBandwidth") : defaults.integer(forKey: "OutOfNetworkBandwidth") - - //Build a device profile + + // Build a device profile let builder = DeviceProfileBuilder() builder.setMaxBitrate(bitrate: maxBitrate) let profile = builder.buildProfile() - + let playbackInfo = PlaybackInfoDto(userId: globalData.user.user_id, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, deviceProfile: profile, autoOpenLiveStream: true) - + DispatchQueue.global(qos: .userInitiated).async { [self] in MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: globalData.user.user_id, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo) .sink(receiveCompletion: { completion in @@ -294,147 +292,146 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe }, receiveValue: { [self] response in playSessionId = response.playSessionId! let mediaSource = response.mediaSources!.first.self! - if(mediaSource.transcodingUrl != nil) { - //Item is being transcoded by request of server + if mediaSource.transcodingUrl != nil { + // Item is being transcoded by request of server let streamURL = URL(string: "\(globalData.server.baseURI!)\(mediaSource.transcodingUrl!)") let item = PlaybackItem() item.videoType = .transcode item.videoUrl = streamURL! - + let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: .embed, codec: "") - subtitleTrackArray.append(disableSubtitleTrack); - - //Loop through media streams and add to array + subtitleTrackArray.append(disableSubtitleTrack) + + // Loop through media streams and add to array for stream in mediaSource.mediaStreams! { - if(stream.type == .subtitle) { + if stream.type == .subtitle { let deliveryUrl = URL(string: "\(globalData.server.baseURI!)\(stream.deliveryUrl!)")! let subtitle = Subtitle(name: stream.displayTitle!, id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec!) - subtitleTrackArray.append(subtitle); + subtitleTrackArray.append(subtitle) } - - if(stream.type == .audio) { + + if stream.type == .audio { let subtitle = AudioTrack(name: stream.displayTitle!, id: Int32(stream.index!)) - if(stream.isDefault! == true) { - selectedAudioTrack = Int32(stream.index!); + if stream.isDefault! == true { + selectedAudioTrack = Int32(stream.index!) } - audioTrackArray.append(subtitle); + audioTrackArray.append(subtitle) } } - - if(selectedAudioTrack == -1) { - if(audioTrackArray.count > 0) { - selectedAudioTrack = audioTrackArray[0].id; + + if selectedAudioTrack == -1 { + if audioTrackArray.count > 0 { + selectedAudioTrack = audioTrackArray[0].id } } - + self.sendPlayReport() - playbackItem = item; + playbackItem = item } else { - //Item will be directly played by the client. - let streamURL: URL = URL(string: "\(globalData.server.baseURI!)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&deviceId=\(globalData.user.device_uuid!)&api_key=\(globalData.authToken)&Tag=\(mediaSource.eTag!)")!; - + // Item will be directly played by the client. + let streamURL: URL = URL(string: "\(globalData.server.baseURI!)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&deviceId=\(globalData.user.device_uuid!)&api_key=\(globalData.authToken)&Tag=\(mediaSource.eTag!)")! + let item = PlaybackItem() item.videoUrl = streamURL item.videoType = .directPlay - + let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: .embed, codec: "") - subtitleTrackArray.append(disableSubtitleTrack); - - //Loop through media streams and add to array + subtitleTrackArray.append(disableSubtitleTrack) + + // Loop through media streams and add to array for stream in mediaSource.mediaStreams! { - if(stream.type == .subtitle) { + if stream.type == .subtitle { let deliveryUrl = URL(string: "\(globalData.server.baseURI!)\(stream.deliveryUrl!)")! let subtitle = Subtitle(name: stream.displayTitle!, id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec!) - subtitleTrackArray.append(subtitle); + subtitleTrackArray.append(subtitle) } - - if(stream.type == .audio) { + + if stream.type == .audio { let subtitle = AudioTrack(name: stream.displayTitle!, id: Int32(stream.index!)) - if(stream.isDefault! == true) { - selectedAudioTrack = Int32(stream.index!); + if stream.isDefault! == true { + selectedAudioTrack = Int32(stream.index!) } - audioTrackArray.append(subtitle); + audioTrackArray.append(subtitle) } } - - if(selectedAudioTrack == -1) { - if(audioTrackArray.count > 0) { - selectedAudioTrack = audioTrackArray[0].id; + + if selectedAudioTrack == -1 { + if audioTrackArray.count > 0 { + selectedAudioTrack = audioTrackArray[0].id } } - + self.sendPlayReport() - playbackItem = item; + playbackItem = item } - + self.setupNowPlayingCC() - + mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl) mediaPlayer.play() print(manifest.userData?.playbackPositionTicks ?? 0) mediaPlayer.jumpForward(Int32(manifest.userData?.playbackPositionTicks ?? 0/10000000)) mediaPlayer.pause() - subtitleTrackArray.forEach() { sub in - if(sub.id != -1 && sub.delivery == .external && sub.codec != "subrip") { + subtitleTrackArray.forEach { sub in + if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" { print("adding subs for id: \(sub.id) w/ url: \(sub.url)") mediaPlayer.addPlaybackSlave(sub.url, type: .subtitle, enforce: false) } } delegate?.showLoadingView(self) - while(mediaPlayer.numberOfSubtitlesTracks != subtitleTrackArray.count - 1) {} - mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack; + while mediaPlayer.numberOfSubtitlesTracks != subtitleTrackArray.count - 1 {} + mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack mediaPlayer.pause() mediaPlayer.play() }) .store(in: &globalData.pendingAPIRequests) } } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - self.tabBarController?.tabBar.isHidden = true; + self.tabBarController?.tabBar.isHidden = true } - - //MARK: VideoPlayerSettings Delegate + + // MARK: VideoPlayerSettings Delegate func subtitleTrackChanged(newTrackID: Int32) { selectedCaptionTrack = newTrackID mediaPlayer.currentVideoSubTitleIndex = newTrackID } - + func audioTrackChanged(newTrackID: Int32) { selectedAudioTrack = newTrackID mediaPlayer.currentAudioTrackIndex = newTrackID } - - - //MARK: VLCMediaPlayer Delegates + + // MARK: VLCMediaPlayer Delegates func mediaPlayerStateChanged(_ aNotification: Notification!) { let currentState: VLCMediaPlayerState = mediaPlayer.state switch currentState { case .stopped : - break; + break case .ended : - break; + break case .playing : print("Video is playing") sendProgressReport(eventName: "unpause") delegate?.hideLoadingView(self) - paused = false; - + paused = false + case .paused : print("Video is paused)") - paused = true; - + paused = true + case .opening : print("Video is opening)") - + case .buffering : print("Video is buffering)") delegate?.showLoadingView(self) mediaPlayer.pause() usleep(10000) mediaPlayer.play() - + case .error : print("Video has error)") sendStopReport() @@ -444,81 +441,81 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe break } } - + func mediaPlayerTimeChanged(_ aNotification: Notification!) { - let time = mediaPlayer.position; - if(time != lastTime) { - paused = false; + let time = mediaPlayer.position + if time != lastTime { + paused = false mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal) seekSlider.setValue(mediaPlayer.position, animated: true) delegate?.hideLoadingView(self) - - let remainingTime = abs(mediaPlayer.remainingTime.intValue)/1000; - let hours = remainingTime / 3600; - let minutes = (remainingTime % 3600) / 60; - let seconds = (remainingTime % 3600) % 60; - var timeTextStr = ""; - if(hours != 0) { - timeTextStr = "\(Int(hours)):\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))"; + + let remainingTime = abs(mediaPlayer.remainingTime.intValue)/1000 + let hours = remainingTime / 3600 + let minutes = (remainingTime % 3600) / 60 + let seconds = (remainingTime % 3600) % 60 + var timeTextStr = "" + if hours != 0 { + timeTextStr = "\(Int(hours)):\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))" } else { - timeTextStr = "\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))"; + timeTextStr = "\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))" } timeText.text = timeTextStr - - if(CACurrentMediaTime() - controlsAppearTime > 5) { + + if CACurrentMediaTime() - controlsAppearTime > 5 { UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: { self.videoControlsView.alpha = 0.0 - }, completion: { (finished: Bool) in - self.videoControlsView.isHidden = true; + }, completion: { (_: Bool) in + self.videoControlsView.isHidden = true self.videoControlsView.alpha = 1 }) - controlsAppearTime = 10000000000000000000000; + controlsAppearTime = 10000000000000000000000 } } else { - paused = true; + paused = true } - lastTime = time; - - if(CACurrentMediaTime() - lastProgressReportTime > 5) { + lastTime = time + + if CACurrentMediaTime() - lastProgressReportTime > 5 { sendProgressReport(eventName: "timeupdate") lastProgressReportTime = CACurrentMediaTime() } } - - //MARK: Jellyfin Playstate updates + + // MARK: Jellyfin Playstate updates func sendProgressReport(eventName: String) { let progressInfo = PlaybackProgressInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: (mediaPlayer.state == .paused), isMuted: false, positionTicks: Int64(mediaPlayer.position * Float(manifest.runTimeTicks!)), playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0") - + PlaystateAPI.reportPlaybackProgress(playbackProgressInfo: progressInfo) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: self.globalData, completion: completion) - }, receiveValue: { response in + }, receiveValue: { _ in print("Playback progress report sent!") }) .store(in: &globalData.pendingAPIRequests) } - + func sendStopReport() { let stopInfo = PlaybackStopInfo(item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, positionTicks: Int64(mediaPlayer.position * Float(manifest.runTimeTicks!)), liveStreamId: nil, playSessionId: playSessionId, failed: nil, nextMediaType: nil, playlistItemId: "playlistItem0", nowPlayingQueue: []) - + PlaystateAPI.reportPlaybackStopped(playbackStopInfo: stopInfo) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: self.globalData, completion: completion) - }, receiveValue: { response in + }, receiveValue: { _ in print("Playback stop report sent!") }) .store(in: &globalData.pendingAPIRequests) } - + func sendPlayReport() { startTime = Int(Date().timeIntervalSince1970) * 10000000 - + let startInfo = PlaybackStartInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: false, isMuted: false, positionTicks: manifest.userData?.playbackPositionTicks, playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0") - + PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: self.globalData, completion: completion) - }, receiveValue: { response in + }, receiveValue: { _ in print("Playback start report sent!") }) .store(in: &globalData.pendingAPIRequests) @@ -528,30 +525,30 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe struct VLCPlayerWithControls: UIViewControllerRepresentable { var item: BaseItemDto @Environment(\.presentationMode) var presentationMode - @EnvironmentObject private var globalData: GlobalData; - + @EnvironmentObject private var globalData: GlobalData + var loadBinding: Binding var pBinding: Binding class Coordinator: NSObject, PlayerViewControllerDelegate { let loadBinding: Binding let pBinding: Binding - + init(loadBinding: Binding, pBinding: Binding) { self.loadBinding = loadBinding self.pBinding = pBinding } - + func hideLoadingView(_ viewController: PlayerViewController) { - self.loadBinding.wrappedValue = false; + self.loadBinding.wrappedValue = false } - + func showLoadingView(_ viewController: PlayerViewController) { - self.loadBinding.wrappedValue = true; + self.loadBinding.wrappedValue = true } - + func exitPlayer(_ viewController: PlayerViewController) { - self.pBinding.wrappedValue = false; + self.pBinding.wrappedValue = false } } @@ -559,14 +556,13 @@ struct VLCPlayerWithControls: UIViewControllerRepresentable { Coordinator(loadBinding: self.loadBinding, pBinding: self.pBinding) } - typealias UIViewControllerType = PlayerViewController func makeUIViewController(context: UIViewControllerRepresentableContext) -> VLCPlayerWithControls.UIViewControllerType { let storyboard = UIStoryboard(name: "VideoPlayer", bundle: nil) let customViewController = storyboard.instantiateViewController(withIdentifier: "VideoPlayer") as! PlayerViewController - customViewController.manifest = item; - customViewController.delegate = context.coordinator; - customViewController.globalData = globalData; + customViewController.manifest = item + customViewController.delegate = context.coordinator + customViewController.globalData = globalData return customViewController } diff --git a/JellyfinPlayer/VideoPlayerSettingsView.swift b/JellyfinPlayer/VideoPlayerSettingsView.swift index 9f9702ae..752601d5 100644 --- a/JellyfinPlayer/VideoPlayerSettingsView.swift +++ b/JellyfinPlayer/VideoPlayerSettingsView.swift @@ -22,7 +22,7 @@ class VideoPlayerSettingsView: UIViewController { contentView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true contentView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true } - + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.delegate?.settingsPopoverDismissed() @@ -30,17 +30,17 @@ class VideoPlayerSettingsView: UIViewController { } struct VideoPlayerSettings: View { - @State var delegate: PlayerViewController! - @State var captionTrack: Int32 = -99; - @State var audioTrack: Int32 = -99; - + @State weak var delegate: PlayerViewController! + @State var captionTrack: Int32 = -99 + @State var audioTrack: Int32 = -99 + init(delegate: PlayerViewController) { self.delegate = delegate } - + var body: some View { - NavigationView() { - Form() { + NavigationView { + Form { Picker("Closed Captions", selection: $captionTrack) { ForEach(delegate.subtitleTrackArray, id: \.id) { caption in Text(caption.name).tag(caption.id) @@ -60,11 +60,11 @@ struct VideoPlayerSettings: View { .navigationTitle("Audio & Captions") .toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { - if(UIDevice.current.userInterfaceIdiom == .phone) { + if UIDevice.current.userInterfaceIdiom == .phone { Button { self.delegate.settingsPopoverDismissed() } label: { - HStack() { + HStack { Image(systemName: "chevron.left") Text("Back").font(.callout) } diff --git a/Shared/Extensions/APIExtensions.swift b/Shared/Extensions/APIExtensions.swift index 8ff2f1eb..ec41d52e 100644 --- a/Shared/Extensions/APIExtensions.swift +++ b/Shared/Extensions/APIExtensions.swift @@ -9,68 +9,68 @@ import Foundation import JellyfinAPI import UIKit -//001fC^ = dark grey plain blurhash +// 001fC^ = dark grey plain blurhash extension BaseItemDto { - - //MARK: Images + + // MARK: Images func getSeriesBackdropImageBlurHash() -> String { - let rawImgURL = self.getSeriesBackdropImage(baseURL: "", maxWidth: 1).absoluteString; - let imgTag = rawImgURL.components(separatedBy: "&tag=")[1]; - - return self.imageBlurHashes?.backdrop?[imgTag] ?? "001fC^"; + let rawImgURL = self.getSeriesBackdropImage(baseURL: "", 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 imgTag = rawImgURL.components(separatedBy: "&tag=")[1]; - - return self.imageBlurHashes?.primary?[imgTag] ?? "001fC^"; + let rawImgURL = self.getSeriesPrimaryImage(baseURL: "", 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 imgTag = rawImgURL.components(separatedBy: "&tag=")[1]; - - return self.imageBlurHashes?.primary?[imgTag] ?? "001fC^"; + let rawImgURL = self.getPrimaryImage(baseURL: "", 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 imgTag = rawImgURL.components(separatedBy: "&tag=")[1]; - - if(rawImgURL.contains("Backdrop")) { - return self.imageBlurHashes?.backdrop?[imgTag] ?? "001fC^"; + let rawImgURL = self.getBackdropImage(baseURL: "", maxWidth: 1).absoluteString + let imgTag = rawImgURL.components(separatedBy: "&tag=")[1] + + if rawImgURL.contains("Backdrop") { + return self.imageBlurHashes?.backdrop?[imgTag] ?? "001fC^" } else { - return self.imageBlurHashes?.primary?[imgTag] ?? "001fC^"; + return self.imageBlurHashes?.primary?[imgTag] ?? "001fC^" } } - + func getBackdropImage(baseURL: String, maxWidth: Int) -> URL { - var imageType = ""; - var imageTag = ""; - - if(self.primaryImageAspectRatio ?? 0.0 < 1.0) { - imageType = "Backdrop"; + var imageType = "" + var imageTag = "" + + if self.primaryImageAspectRatio ?? 0.0 < 1.0 { + imageType = "Backdrop" imageTag = (self.backdropImageTags ?? [""])[0] } else { - imageType = "Primary"; + imageType = "Primary" imageTag = self.imageTags?["Primary"] ?? "" } - - if(imageTag == "") { - imageType = "Backdrop"; + + if imageTag == "" { + imageType = "Backdrop" imageTag = self.parentBackdropImageTags?[0] ?? "" } let x = UIScreen.main.nativeScale * CGFloat(maxWidth) let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)" return URL(string: urlString)! } - + func getSeriesBackdropImage(baseURL: String, maxWidth: Int) -> URL { - let imageType = "Backdrop"; - let imageTag = (self.parentBackdropImageTags ?? [])[0]; - + let imageType = "Backdrop" + let imageTag = (self.parentBackdropImageTags ?? [])[0] + print(imageType) print(imageTag) @@ -78,29 +78,29 @@ extension BaseItemDto { let urlString = "\(baseURL)/Items/\(self.parentBackdropItemId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)" return URL(string: urlString)! } - + func getSeriesPrimaryImage(baseURL: String, maxWidth: Int) -> URL { - let imageType = "Primary"; + 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=85&tag=\(imageTag)" return URL(string: urlString)! } - + func getPrimaryImage(baseURL: String, maxWidth: Int) -> URL { - let imageType = "Primary"; - var imageTag = self.imageTags?["Primary"] ?? ""; - - if(imageTag == "") { + let imageType = "Primary" + var imageTag = self.imageTags?["Primary"] ?? "" + + if imageTag == "" { imageTag = self.seriesPrimaryImageTag ?? "" } let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - + let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)" return URL(string: urlString)! } - - //MARK: Calculations + + // MARK: Calculations func getItemRuntime() -> String { let seconds: Int = Int(self.runTimeTicks!) / 10_000_000 let hours = (seconds / 3600) @@ -111,12 +111,12 @@ extension BaseItemDto { return "\(String(minutes).leftPad(toWidth: 2, withString: "0"))m" } } - + func getItemProgressString() -> String { - if(self.userData?.playbackPositionTicks == nil || self.userData?.playbackPositionTicks == 0) { - return ""; + if self.userData?.playbackPositionTicks == nil || self.userData?.playbackPositionTicks == 0 { + return "" } - + let remainingSecs = Int(self.runTimeTicks! - (self.userData?.playbackPositionTicks!)!) / 10_000_000 let proghours = Int(remainingSecs / 3600) let progminutes = Int((Int(remainingSecs) - (proghours * 3600)) / 60) @@ -130,19 +130,19 @@ extension BaseItemDto { extension BaseItemPerson { func getImage(baseURL: String, maxWidth: Int) -> URL { - let imageType = "Primary"; + let imageType = "Primary" let imageTag = self.primaryImageTag ?? "" let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - + let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)" return URL(string: urlString)! } - + func getBlurHash() -> String { - let rawImgURL = self.getImage(baseURL: "", maxWidth: 1).absoluteString; - let imgTag = rawImgURL.components(separatedBy: "&tag=")[1]; - - return self.imageBlurHashes?.primary?[imgTag] ?? "001fC^"; + let rawImgURL = self.getImage(baseURL: "", maxWidth: 1).absoluteString + let imgTag = rawImgURL.components(separatedBy: "&tag=")[1] + + return self.imageBlurHashes?.primary?[imgTag] ?? "001fC^" } } diff --git a/Shared/Extensions/BlurHashDecode.swift b/Shared/Extensions/BlurHashDecode.swift index 867f8a84..ea29b29d 100644 --- a/Shared/Extensions/BlurHashDecode.swift +++ b/Shared/Extensions/BlurHashDecode.swift @@ -115,14 +115,12 @@ private func signPow(_ value: Float, _ exp: Float) -> Float { private func linearTosRGB(_ value: Float) -> Int { let v = max(0, min(1, value)) - if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) } - else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) } + if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) } else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) } } private func sRGBToLinear(_ value: Type) -> Float { let v = Float(Int64(value)) / 255 - if v <= 0.04045 { return v / 12.92 } - else { return pow((v + 0.055) / 1.055, 2.4) } + if v <= 0.04045 { return v / 12.92 } else { return pow((v + 0.055) / 1.055, 2.4) } } private let encodeCharacters: [String] = { diff --git a/Shared/Extensions/CollectionExtensions.swift b/Shared/Extensions/CollectionExtensions.swift index 9bbd3c50..e295933f 100644 --- a/Shared/Extensions/CollectionExtensions.swift +++ b/Shared/Extensions/CollectionExtensions.swift @@ -7,7 +7,6 @@ import Foundation - public extension Collection { /// SwifterSwift: Safe protects the array from out of bounds by use of optional. diff --git a/Shared/Extensions/HandleAPIRequestCompletion.swift b/Shared/Extensions/HandleAPIRequestCompletion.swift index 5e522912..621eabfa 100644 --- a/Shared/Extensions/HandleAPIRequestCompletion.swift +++ b/Shared/Extensions/HandleAPIRequestCompletion.swift @@ -15,11 +15,11 @@ func HandleAPIRequestCompletion(globalData: GlobalData, completion: Subscribers. break case .failure(let error): if let err = error as? ErrorResponse { - switch(err){ + switch err { case .error(401, _, _, _): - globalData.expiredCredentials = true; - case .error(_, _, _, _): - globalData.networkError = true; + globalData.expiredCredentials = true + case .error: + globalData.networkError = true } } break diff --git a/Shared/Extensions/ParallaxHeader.swift b/Shared/Extensions/ParallaxHeader.swift index ab705b99..d5624f74 100644 --- a/Shared/Extensions/ParallaxHeader.swift +++ b/Shared/Extensions/ParallaxHeader.swift @@ -19,8 +19,7 @@ struct ParallaxHeaderScrollView Content) - { + content: @escaping () -> Content) { self.header = header self.staticOverlayView = staticOverlayView self.overlayAlignment = overlayAlignment diff --git a/Shared/Extensions/StringExtensions.swift b/Shared/Extensions/StringExtensions.swift index 9ade1f72..7261ddb9 100644 --- a/Shared/Extensions/StringExtensions.swift +++ b/Shared/Extensions/StringExtensions.swift @@ -15,7 +15,7 @@ extension String { return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith) } catch { return self } } - + func leftPad(toWidth width: Int, withString string: String?) -> String { let paddingString = string ?? " " @@ -32,4 +32,3 @@ extension String { return "\(padString)\(self)" } } - diff --git a/Shared/Typings/Typings.swift b/Shared/Typings/Typings.swift index 8d4a08ae..b131c3d7 100644 --- a/Shared/Typings/Typings.swift +++ b/Shared/Typings/Typings.swift @@ -32,14 +32,14 @@ class GlobalData: ObservableObject { @Published var authToken: String = "" @Published var server: Server! @Published var authHeader: String = "" - @Published var isInNetwork: Bool = true; - @Published var networkError: Bool = false; - @Published var expiredCredentials: Bool = false; - var pendingAPIRequests = Set(); + @Published var isInNetwork: Bool = true + @Published var networkError: Bool = false + @Published var expiredCredentials: Bool = false + var pendingAPIRequests = Set() } extension GlobalData: Equatable { - + static func == (lhs: GlobalData, rhs: GlobalData) -> Bool { lhs.user == rhs.user && lhs.authToken == rhs.authToken diff --git a/Shared/ViewModel/SettingsViewModel.swift b/Shared/ViewModel/SettingsViewModel.swift index 564f69a5..cf3fe59a 100644 --- a/Shared/ViewModel/SettingsViewModel.swift +++ b/Shared/ViewModel/SettingsViewModel.swift @@ -10,12 +10,12 @@ import Foundation struct UserSettings: Decodable { - var LocalMaxBitrate: Int; - var RemoteMaxBitrate: Int; - var AutoSelectSubtitles: Bool; - var AutoSelectSubtitlesLangcode: String; - var SubtitlePositionOffset: Int; - var SubtitleFontName: String; + var LocalMaxBitrate: Int + var RemoteMaxBitrate: Int + var AutoSelectSubtitles: Bool + var AutoSelectSubtitlesLangcode: String + var SubtitlePositionOffset: Int + var SubtitleFontName: String } struct Bitrates: Codable, Hashable { diff --git a/WidgetExtension/NextUpWidget.swift b/WidgetExtension/NextUpWidget.swift index 76d84797..27d7b960 100644 --- a/WidgetExtension/NextUpWidget.swift +++ b/WidgetExtension/NextUpWidget.swift @@ -380,7 +380,7 @@ struct NextUpWidget_Previews: PreviewProvider { (.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"), UIImage(named: "WidgetHeaderSymbol")), (.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemMedium)) @@ -391,7 +391,7 @@ struct NextUpWidget_Previews: PreviewProvider { (.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"), UIImage(named: "WidgetHeaderSymbol")), (.init(name: "Name2", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series2"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemLarge)) @@ -406,7 +406,7 @@ struct NextUpWidget_Previews: PreviewProvider { (.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"), UIImage(named: "WidgetHeaderSymbol")), (.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemMedium)) @@ -418,7 +418,7 @@ struct NextUpWidget_Previews: PreviewProvider { (.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"), UIImage(named: "WidgetHeaderSymbol")), (.init(name: "Name2", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series2"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemLarge)) @@ -431,7 +431,7 @@ struct NextUpWidget_Previews: PreviewProvider { NextUpEntryView(entry: .init(date: Date(), items: [ (.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemMedium)) @@ -441,7 +441,7 @@ struct NextUpWidget_Previews: PreviewProvider { (.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"), UIImage(named: "WidgetHeaderSymbol")), (.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemLarge)) diff --git a/WidgetExtension/WidgetEnvironment.swift b/WidgetExtension/WidgetEnvironment.swift index e6fcdfa1..48c17eda 100644 --- a/WidgetExtension/WidgetEnvironment.swift +++ b/WidgetExtension/WidgetEnvironment.swift @@ -14,21 +14,21 @@ import UIKit final class WidgetEnvironment { static let shared = WidgetEnvironment() - + var server: Server? var user: SignedInUser? var header: String? - + init() { update() } - + func update() { let serverRequest = NSFetchRequest(entityName: "Server") let servers = try? PersistenceController.shared.container.viewContext.fetch(serverRequest) as? [Server] let savedUserRequest = NSFetchRequest(entityName: "SignedInUser") let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest) as? [SignedInUser] - + server = servers?.first user = savedUsers?.first