From 4c76ac1438543d778a90f38440ebe53893615948 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Tue, 25 Jan 2022 12:35:38 -0700 Subject: [PATCH 01/25] new log structure --- .../MainCoordinator/iOSMainCoordinator.swift | 4 +- .../MainCoordinator/tvOSMainCoordinator.swift | 4 +- Shared/Errors/NetworkError.swift | 14 ++-- .../BaseItemDto+VideoPlayerViewModel.swift | 2 +- Shared/ServerDiscovery/ServerDiscovery.swift | 4 +- Shared/Singleton/LogManager.swift | 69 +++++++++-------- .../ViewModels/ConnectToServerViewModel.swift | 4 +- Shared/ViewModels/HomeViewModel.swift | 10 +-- .../ItemViewModel/SeasonItemViewModel.swift | 4 +- .../ItemViewModel/SeriesItemViewModel.swift | 6 +- Shared/ViewModels/LatestMediaViewModel.swift | 4 +- .../ViewModels/LiveTVChannelsViewModel.swift | 10 +-- .../ViewModels/LiveTVProgramsViewModel.swift | 14 ++-- Shared/ViewModels/SettingsViewModel.swift | 4 +- Shared/ViewModels/UserSignInViewModel.swift | 4 +- .../VideoPlayerViewModel.swift | 8 +- Shared/ViewModels/ViewModel.swift | 10 +-- .../CinematicItemViewTopRow.swift | 4 +- .../VideoPlayer/VLCPlayerViewController.swift | 6 +- Swiftfin.xcodeproj/project.pbxproj | 74 +++++++++---------- .../xcshareddata/swiftpm/Package.resolved | 8 +- Swiftfin/App/AppDelegate.swift | 1 + Swiftfin/Info.plist | 4 + .../Landscape/ItemLandscapeMainView.swift | 2 +- .../ItemPortraitHeaderOverlayView.swift | 4 +- .../VideoPlayer/VLCPlayerViewController.swift | 6 +- 26 files changed, 147 insertions(+), 137 deletions(-) diff --git a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift index 7e47fa65..79330528 100644 --- a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift @@ -61,13 +61,13 @@ final class MainCoordinator: NavigationCoordinatable { @objc func didLogIn() { - LogManager.shared.log.info("Received `didSignIn` from SwiftfinNotificationCenter.") + LogManager.log.info("Received `didSignIn` from SwiftfinNotificationCenter.") root(\.mainTab) } @objc func didLogOut() { - LogManager.shared.log.info("Received `didSignOut` from SwiftfinNotificationCenter.") + LogManager.log.info("Received `didSignOut` from SwiftfinNotificationCenter.") root(\.serverList) } diff --git a/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift index 632b7790..b09bcfda 100644 --- a/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift @@ -47,13 +47,13 @@ final class MainCoordinator: NavigationCoordinatable { @objc func didLogIn() { - LogManager.shared.log.info("Received `didSignIn` from NSNotificationCenter.") + LogManager.log.info("Received `didSignIn` from NSNotificationCenter.") root(\.mainTab) } @objc func didLogOut() { - LogManager.shared.log.info("Received `didSignOut` from NSNotificationCenter.") + LogManager.log.info("Received `didSignOut` from NSNotificationCenter.") root(\.serverList) } diff --git a/Shared/Errors/NetworkError.swift b/Shared/Errors/NetworkError.swift index b6cf2c73..0930e90a 100644 --- a/Shared/Errors/NetworkError.swift +++ b/Shared/Errors/NetworkError.swift @@ -44,19 +44,19 @@ enum NetworkError: Error { switch logConstructor.level { case .trace: - logFunction = LogManager.shared.log.trace + logFunction = LogManager.log.trace case .debug: - logFunction = LogManager.shared.log.debug + logFunction = LogManager.log.debug case .information: - logFunction = LogManager.shared.log.info + logFunction = LogManager.log.info case .warning: - logFunction = LogManager.shared.log.warning + logFunction = LogManager.log.warning case .error: - logFunction = LogManager.shared.log.error + logFunction = LogManager.log.error case .critical: - logFunction = LogManager.shared.log.critical + logFunction = LogManager.log.critical case ._none: - logFunction = LogManager.shared.log.debug + logFunction = LogManager.log.debug } logFunction(logConstructor.message, logConstructor.tag, logConstructor.function, logConstructor.file, logConstructor.line) diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift index c658e9e1..6a8ec3bc 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift @@ -14,7 +14,7 @@ import UIKit extension BaseItemDto { func createVideoPlayerViewModel() -> AnyPublisher<[VideoPlayerViewModel], Error> { - LogManager.shared.log.debug("Creating video player view model for item: \(id ?? "")") + LogManager.log.debug("Creating video player view model for item: \(id ?? "")") let builder = DeviceProfileBuilder() // TODO: fix bitrate settings diff --git a/Shared/ServerDiscovery/ServerDiscovery.swift b/Shared/ServerDiscovery/ServerDiscovery.swift index 81f6de75..2ee6122b 100644 --- a/Shared/ServerDiscovery/ServerDiscovery.swift +++ b/Shared/ServerDiscovery/ServerDiscovery.swift @@ -59,7 +59,7 @@ public class ServerDiscovery { func receiveHandler(_ ipAddress: String, _ port: Int, _ data: Data) { do { let response = try JSONDecoder().decode(ServerLookupResponse.self, from: data) - LogManager.shared.log.debug("Received JellyfinServer from \"\(response.name)\"", tag: "ServerDiscovery") + LogManager.log.debug("Received JellyfinServer from \"\(response.name)\"", tag: "ServerDiscovery") completion(response) } catch { completion(nil) @@ -68,7 +68,7 @@ public class ServerDiscovery { self.broadcastConn.handler = receiveHandler do { try broadcastConn.sendBroadcast("Who is JellyfinServer?") - LogManager.shared.log.debug("Discovery broadcast sent", tag: "ServerDiscovery") + LogManager.log.debug("Discovery broadcast sent", tag: "ServerDiscovery") } catch { print(error) } diff --git a/Shared/Singleton/LogManager.swift b/Shared/Singleton/LogManager.swift index de1af46b..205fd686 100644 --- a/Shared/Singleton/LogManager.swift +++ b/Shared/Singleton/LogManager.swift @@ -10,39 +10,44 @@ import Foundation import Puppy class LogManager { - static let shared = LogManager() - let log = Puppy() + + static let log = Puppy() + + static func setup() { + + let logsDirectory = getDocumentsDirectory().appendingPathComponent("logs", isDirectory: true) + + do { + try FileManager.default.createDirectory(atPath: logsDirectory.path, + withIntermediateDirectories: true, + attributes: nil) + } catch { + // logs directory already created + } + + let logFileURL = logsDirectory.appendingPathComponent("swiftfin_log.log") + + let fileRotationLogger = try! FileRotationLogger("org.jellyfin.swiftfin.logger.file-rotation", + fileURL: logFileURL) + fileRotationLogger.suffixExtension = .numbering + fileRotationLogger.maxFileSize = 10 * 1024 + fileRotationLogger.maxArchivedFilesCount = 5 + fileRotationLogger.format = LogFormatter() + + let consoleLogger = ConsoleLogger("org.jellyfin.swiftfin.logger.console") + consoleLogger.format = LogFormatter() + + log.add(fileRotationLogger, withLevel: .debug) + log.add(consoleLogger, withLevel: .debug) + } + + private static func getDocumentsDirectory() -> URL { + // find all possible documents directories for this user + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - init() { - let console = ConsoleLogger("com.swiftfin.ConsoleLogger") - let fileURL = self.getDocumentsDirectory().appendingPathComponent("logs.txt") - let FM = FileManager() - _ = try? FM.removeItem(at: fileURL) - - do { - let file = try FileLogger("com.swiftfin", fileURL: fileURL) - file.format = LogFormatter() - log.add(file, withLevel: .debug) - } catch let err { - log.error("Couldn't initialize file logger.") - print(err) - } - console.format = LogFormatter() - log.add(console, withLevel: .debug) - log.info("Logger initialized.") - } - - func logFileURL() -> URL { - self.getDocumentsDirectory().appendingPathComponent("logs.txt") - } - - func getDocumentsDirectory() -> URL { - // find all possible documents directories for this user - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - - // just send back the first one, which ought to be the only one - return paths[0] - } + // just send back the first one, which ought to be the only one + return paths[0] + } } class LogFormatter: LogFormattable { diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index 426b5a37..be928f17 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -55,7 +55,7 @@ final class ConnectToServerViewModel: ViewModel { let trimmedURI = uri.trimmingCharacters(in: .whitespaces) - LogManager.shared.log.debug("Attempting to connect to server at \"\(trimmedURI)\"", tag: "connectToServer") + LogManager.log.debug("Attempting to connect to server at \"\(trimmedURI)\"", tag: "connectToServer") SessionManager.main.connectToServer(with: trimmedURI) .trackActivity(loading) .sink(receiveCompletion: { completion in @@ -103,7 +103,7 @@ final class ConnectToServerViewModel: ViewModel { } } }, receiveValue: { server in - LogManager.shared.log.debug("Connected to server at \"\(uri)\"", tag: "connectToServer") + LogManager.log.debug("Connected to server at \"\(uri)\"", tag: "connectToServer") self.router?.route(to: \.userSignIn, server) }) .store(in: &cancellables) diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index 9df68555..fdff81f2 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -64,7 +64,7 @@ final class HomeViewModel: ViewModel { @objc func refresh() { - LogManager.shared.log.debug("Refresh called.") + LogManager.log.debug("Refresh called.") refreshLibrariesLatest() refreshLatestAddedItems() @@ -90,7 +90,7 @@ final class HomeViewModel: ViewModel { var newLibraries: [BaseItemDto] = [] response.items!.forEach { item in - LogManager.shared.log + LogManager.log .debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")") if item.collectionType == "movies" || item.collectionType == "tvshows" { newLibraries.append(item) @@ -148,7 +148,7 @@ final class HomeViewModel: ViewModel { self.handleAPIRequestError(completion: completion) } } receiveValue: { items in - LogManager.shared.log.debug("Retrieved \(String(items.count)) resume items") + LogManager.log.debug("Retrieved \(String(items.count)) resume items") self.latestAddedItems = items } @@ -179,7 +179,7 @@ final class HomeViewModel: ViewModel { self.handleAPIRequestError(completion: completion) } }, receiveValue: { response in - LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) resume items") + LogManager.log.debug("Retrieved \(String(response.items!.count)) resume items") self.resumeItems = response.items ?? [] }) @@ -224,7 +224,7 @@ final class HomeViewModel: ViewModel { self.handleAPIRequestError(completion: completion) } }, receiveValue: { response in - LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) nextup items") + LogManager.log.debug("Retrieved \(String(response.items!.count)) nextup items") self.nextUpItems = response.items ?? [] }) diff --git a/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift b/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift index ac2f09bc..7e7a1dca 100644 --- a/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift @@ -47,7 +47,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager { } // private func requestEpisodes() { -// LogManager.shared.log +// LogManager.log // .debug("Getting episodes in season \(item.id!) (\(item.name!)) of show \(item.seriesId!) (\(item.seriesName!))") // TvShowsAPI.getEpisodes(seriesId: item.seriesId ?? "", userId: SessionManager.main.currentLogin.user.id, // fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], @@ -57,7 +57,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager { // self?.handleAPIRequestError(completion: completion) // }, receiveValue: { [weak self] response in // self?.episodes = response.items ?? [] -// LogManager.shared.log.debug("Retrieved \(String(self?.episodes.count ?? 0)) episodes") +// LogManager.log.debug("Retrieved \(String(self?.episodes.count ?? 0)) episodes") // // self?.setNextUpInSeason() // }) diff --git a/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift b/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift index 05dfb4e8..55ac91ea 100644 --- a/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift @@ -43,7 +43,7 @@ final class SeriesItemViewModel: ItemViewModel { private func getNextUp() { - LogManager.shared.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))") + LogManager.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))") TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], seriesId: self.item.id!, @@ -78,7 +78,7 @@ final class SeriesItemViewModel: ItemViewModel { } private func requestSeasons() { - LogManager.shared.log.debug("Getting seasons of show \(self.item.id!) (\(self.item.name!))") + LogManager.log.debug("Getting seasons of show \(self.item.id!) (\(self.item.name!))") TvShowsAPI.getSeasons(seriesId: item.id ?? "", userId: SessionManager.main.currentLogin.user.id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], isMissing: Defaults[.shouldShowMissingSeasons] ? nil : false, @@ -88,7 +88,7 @@ final class SeriesItemViewModel: ItemViewModel { self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in self?.seasons = response.items ?? [] - LogManager.shared.log.debug("Retrieved \(String(self?.seasons.count ?? 0)) seasons") + LogManager.log.debug("Retrieved \(String(self?.seasons.count ?? 0)) seasons") }) .store(in: &cancellables) } diff --git a/Shared/ViewModels/LatestMediaViewModel.swift b/Shared/ViewModels/LatestMediaViewModel.swift index aac3cbf1..85012299 100644 --- a/Shared/ViewModels/LatestMediaViewModel.swift +++ b/Shared/ViewModels/LatestMediaViewModel.swift @@ -25,7 +25,7 @@ final class LatestMediaViewModel: ViewModel { } func requestLatestMedia() { - LogManager.shared.log.debug("Requesting latest media for user id \(SessionManager.main.currentLogin.user.id)") + LogManager.log.debug("Requesting latest media for user id \(SessionManager.main.currentLogin.user.id)") UserLibraryAPI.getLatestMedia(userId: SessionManager.main.currentLogin.user.id, parentId: library.id ?? "", fields: [ @@ -43,7 +43,7 @@ final class LatestMediaViewModel: ViewModel { self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in self?.items = response - LogManager.shared.log.debug("Retrieved \(String(self?.items.count ?? 0)) items") + LogManager.log.debug("Retrieved \(String(self?.items.count ?? 0)) items") }) .store(in: &cancellables) } diff --git a/Shared/ViewModels/LiveTVChannelsViewModel.swift b/Shared/ViewModels/LiveTVChannelsViewModel.swift index f2f61acd..c609e7b0 100644 --- a/Shared/ViewModels/LiveTVChannelsViewModel.swift +++ b/Shared/ViewModels/LiveTVChannelsViewModel.swift @@ -68,7 +68,7 @@ final class LiveTVChannelsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] _ in - LogManager.shared.log.debug("Received Guide Info") + LogManager.log.debug("Received Guide Info") guard let self = self else { return } self.getChannels() }) @@ -86,7 +86,7 @@ final class LiveTVChannelsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.shared.log.debug("Received \(response.items?.count ?? 0) Channels") + LogManager.log.debug("Received \(response.items?.count ?? 0) Channels") guard let self = self else { return } self.channels = response.items ?? [] self.getPrograms() @@ -97,7 +97,7 @@ final class LiveTVChannelsViewModel: ViewModel { private func getPrograms() { // http://192.168.1.50:8096/LiveTv/Programs guard !channels.isEmpty else { - LogManager.shared.log.debug("Cannot get programs, channels list empty. ") + LogManager.log.debug("Cannot get programs, channels list empty. ") return } let channelIds = channels.compactMap(\.id) @@ -121,7 +121,7 @@ final class LiveTVChannelsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.shared.log.debug("Received \(response.items?.count ?? 0) Programs") + LogManager.log.debug("Received \(response.items?.count ?? 0) Programs") guard let self = self else { return } self.programs = response.items ?? [] self.channelPrograms = self.processChannelPrograms() @@ -173,7 +173,7 @@ final class LiveTVChannelsViewModel: ViewModel { } timer = Timer(fire: nextMinute, interval: 60 * 10, repeats: true) { [weak self] _ in guard let self = self else { return } - LogManager.shared.log.debug("LiveTVChannels schedule check...") + LogManager.log.debug("LiveTVChannels schedule check...") DispatchQueue.global(qos: .background).async { let newChanPrgs = self.processChannelPrograms() DispatchQueue.main.async { diff --git a/Shared/ViewModels/LiveTVProgramsViewModel.swift b/Shared/ViewModels/LiveTVProgramsViewModel.swift index f3ea55df..99c5e8e9 100644 --- a/Shared/ViewModels/LiveTVProgramsViewModel.swift +++ b/Shared/ViewModels/LiveTVProgramsViewModel.swift @@ -47,7 +47,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.shared.log.debug("Received \(response.items?.count ?? 0) Channels") + LogManager.log.debug("Received \(response.items?.count ?? 0) Channels") guard let self = self else { return } if let chans = response.items { for chan in chans { @@ -78,7 +78,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.shared.log.debug("Received \(String(response.items?.count ?? 0)) Recommended Programs") + LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Recommended Programs") guard let self = self else { return } self.recommendedItems = response.items ?? [] }) @@ -103,7 +103,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.shared.log.debug("Received \(String(response.items?.count ?? 0)) Series Items") + LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Series Items") guard let self = self else { return } self.seriesItems = response.items ?? [] }) @@ -128,7 +128,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.shared.log.debug("Received \(String(response.items?.count ?? 0)) Movie Items") + LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Movie Items") guard let self = self else { return } self.movieItems = response.items ?? [] }) @@ -149,7 +149,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.shared.log.debug("Received \(String(response.items?.count ?? 0)) Sports Items") + LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Sports Items") guard let self = self else { return } self.sportsItems = response.items ?? [] }) @@ -170,7 +170,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.shared.log.debug("Received \(String(response.items?.count ?? 0)) Kids Items") + LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Kids Items") guard let self = self else { return } self.kidsItems = response.items ?? [] }) @@ -191,7 +191,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.shared.log.debug("Received \(String(response.items?.count ?? 0)) News Items") + LogManager.log.debug("Received \(String(response.items?.count ?? 0)) News Items") guard let self = self else { return } self.newsItems = response.items ?? [] }) diff --git a/Shared/ViewModels/SettingsViewModel.swift b/Shared/ViewModels/SettingsViewModel.swift index 92a6d6bd..a88a23ca 100644 --- a/Shared/ViewModels/SettingsViewModel.swift +++ b/Shared/ViewModels/SettingsViewModel.swift @@ -31,10 +31,10 @@ final class SettingsViewModel: ObservableObject { do { self.bitrates = try JSONDecoder().decode([Bitrates].self, from: jsonData) } catch { - LogManager.shared.log.error("Error converting processed JSON into Swift compatible schema.") + LogManager.log.error("Error converting processed JSON into Swift compatible schema.") } } catch { - LogManager.shared.log.error("Error processing JSON file `bitrates.json`") + LogManager.log.error("Error processing JSON file `bitrates.json`") } // Track languages diff --git a/Shared/ViewModels/UserSignInViewModel.swift b/Shared/ViewModels/UserSignInViewModel.swift index fbe5d501..de1d1445 100644 --- a/Shared/ViewModels/UserSignInViewModel.swift +++ b/Shared/ViewModels/UserSignInViewModel.swift @@ -30,8 +30,8 @@ final class UserSignInViewModel: ViewModel { } func login(username: String, password: String) { - LogManager.shared.log.debug("Attempting to login to server at \"\(server.currentURI)\"", tag: "login") - LogManager.shared.log.debug("username: \(username), password: \(password)", tag: "login") + LogManager.log.debug("Attempting to login to server at \"\(server.currentURI)\"", tag: "login") + LogManager.log.debug("username: \(username), password: \(password)", tag: "login") SessionManager.main.loginUser(server: server, username: username, password: password) .trackActivity(loading) diff --git a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift index 6ce29063..06840613 100644 --- a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift @@ -472,7 +472,7 @@ extension VideoPlayerViewModel { .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { _ in - LogManager.shared.log.debug("Start report sent for item: \(self.item.id ?? "No ID")") + LogManager.log.debug("Start report sent for item: \(self.item.id ?? "No ID")") } .store(in: &cancellables) } @@ -508,7 +508,7 @@ extension VideoPlayerViewModel { .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { _ in - LogManager.shared.log.debug("Pause report sent for item: \(self.item.id ?? "No ID")") + LogManager.log.debug("Pause report sent for item: \(self.item.id ?? "No ID")") } .store(in: &cancellables) } @@ -553,7 +553,7 @@ extension VideoPlayerViewModel { .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { _ in - LogManager.shared.log.debug("Playback progress sent for item: \(self.item.id ?? "No ID")") + LogManager.log.debug("Playback progress sent for item: \(self.item.id ?? "No ID")") } .store(in: &cancellables) @@ -580,7 +580,7 @@ extension VideoPlayerViewModel { .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { _ in - LogManager.shared.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")") + LogManager.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")") SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSendStopReport, object: self.item.id) } diff --git a/Shared/ViewModels/ViewModel.swift b/Shared/ViewModels/ViewModel.swift index 8531106e..6e7d5b5f 100644 --- a/Shared/ViewModels/ViewModel.swift +++ b/Shared/ViewModels/ViewModel.swift @@ -43,16 +43,16 @@ class ViewModel: ObservableObject { case .error(-1, _, _, _): networkError = .URLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor) // Use the errorResponse description for debugging, rather than the user-facing friendly description which may not be implemented - LogManager.shared.log + LogManager.log .error("Request failed: URL request failed with error \(networkError.errorMessage.code): \(errorResponse.localizedDescription)") case .error(-2, _, _, _): networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor) - LogManager.shared.log + LogManager.log .error("Request failed: HTTP URL request failed with description: \(errorResponse.localizedDescription)") default: networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor) // Able to use user-facing friendly description here since just HTTP status codes - LogManager.shared.log + LogManager.log .error("Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.logConstructor.message)\n\(error.localizedDescription)") } @@ -67,7 +67,7 @@ class ViewModel: ObservableObject { displayMessage: swiftfinError.errorDescription ?? "", logConstructor: logConstructor) self.errorMessage = errorMessage - LogManager.shared.log.error("Request failed: \(swiftfinError.errorDescription ?? "")") + LogManager.log.error("Request failed: \(swiftfinError.errorDescription ?? "")") default: let genericErrorMessage = ErrorMessage(code: ErrorMessage.noShowErrorCode, @@ -75,7 +75,7 @@ class ViewModel: ObservableObject { displayMessage: error.localizedDescription, logConstructor: logConstructor) self.errorMessage = genericErrorMessage - LogManager.shared.log.error("Request failed: Generic error - \(error.localizedDescription)") + LogManager.log.error("Request failed: Generic error - \(error.localizedDescription)") } } } diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift index f3c3da8f..38099a00 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift @@ -62,7 +62,7 @@ struct CinematicItemViewTopRow: View { if let selectedVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel { itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel) } else { - LogManager.shared.log.error("Attempted to play item but no playback information available") + LogManager.log.error("Attempted to play item but no playback information available") } } label: { HStack(spacing: 15) { @@ -85,7 +85,7 @@ struct CinematicItemViewTopRow: View { selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel) } else { - LogManager.shared.log.error("Attempted to play item but no playback information available") + LogManager.log.error("Attempted to play item but no playback information available") } } label: { Label(L10n.playFromBeginning, systemImage: "gobackward") diff --git a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift index 1ba5b04a..e3883b9d 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift @@ -463,11 +463,11 @@ extension VLCPlayerViewController { viewModel = newViewModel if viewModel.streamType == .direct { - LogManager.shared.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? "--")") + LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? "--")") } else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] { - LogManager.shared.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? "--")") + LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? "--")") } else { - LogManager.shared.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? "--")") + LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? "--")") } } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 1037861e..8f66867a 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -54,12 +54,9 @@ 535870AA2669D8AE00D05A09 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389277B263CC3DB0035E14B /* BlurHashDecode.swift */; }; 535870AD2669D8DD00D05A09 /* Typings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* Typings.swift */; }; 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; }; - 53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = 53649AAC269CFAEA00A2D8B7 /* Puppy */; }; - 53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = 53649AAE269CFAF600A2D8B7 /* Puppy */; }; 53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; }; 53649AB2269D019100A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; }; 53649AB3269D3F5B00A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; }; - 53649AB5269D423A00A2D8B7 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = 53649AB4269D423A00A2D8B7 /* Puppy */; }; 5364F455266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */; }; 5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */; }; 536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D73267BA8170004248C /* BackgroundManager.swift */; }; @@ -287,6 +284,9 @@ E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; + E1347DB2279E3C6200BC6161 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1347DB1279E3C6200BC6161 /* Puppy */; }; + E1347DB4279E3C9E00BC6161 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1347DB3279E3C9E00BC6161 /* Puppy */; }; + E1347DB6279E3CA500BC6161 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1347DB5279E3CA500BC6161 /* Puppy */; }; E1361DA7278FA7A300BEC523 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = E1361DA6278FA7A300BEC523 /* NukeUI */; }; E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1384943278036C70024FB48 /* VLCPlayerViewController.swift */; }; E13849452780370B0024FB48 /* PlaybackSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */; }; @@ -805,7 +805,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */, E11D83AF278FA998006E9776 /* NukeUI in Frameworks */, E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */, E1002B6B2793E36600E47059 /* Algorithms in Frameworks */, @@ -818,6 +817,7 @@ E1AE8E7E2789136D00FBDDAA /* Nuke in Frameworks */, E178857D278037FD0094FBCF /* JellyfinAPI in Frameworks */, E12186DE2718F1C50010884C /* Defaults in Frameworks */, + E1347DB6279E3CA500BC6161 /* Puppy in Frameworks */, 53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */, 9EA03141D129DC2763660E29 /* Pods_Swiftfin_tvOS.framework in Frameworks */, ); @@ -829,7 +829,6 @@ files = ( E13DD3D327168E65009D4DAF /* Defaults in Frameworks */, E1361DA7278FA7A300BEC523 /* NukeUI in Frameworks */, - 53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */, E1002B682793CFBA00E47059 /* Algorithms in Frameworks */, E10EAA4D277BB716000269ED /* Sliders in Frameworks */, 62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */, @@ -840,6 +839,7 @@ E1AE8E7C2789135A00FBDDAA /* Nuke in Frameworks */, 625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */, E1B6DCE8271A23780015B715 /* CombineExt in Frameworks */, + E1347DB2279E3C6200BC6161 /* Puppy in Frameworks */, E10EAA45277BB646000269ED /* JellyfinAPI in Frameworks */, EEAF6CD6B3433126DEBC7E87 /* Pods_Swiftfin_iOS.framework in Frameworks */, ); @@ -849,11 +849,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E1347DB4279E3C9E00BC6161 /* Puppy in Frameworks */, E1D7E5A827892566009D0EF7 /* Nuke in Frameworks */, 628B95242670CABD0091AF3B /* SwiftUI.framework in Frameworks */, 531ABF6C2671F5CC00C0FE20 /* WidgetKit.framework in Frameworks */, E13DD3DD27175CE3009D4DAF /* Defaults in Frameworks */, - 53649AB5269D423A00A2D8B7 /* Puppy in Frameworks */, 536D3D7D267BD5F90004248C /* ActivityIndicator in Frameworks */, E13DD3CF27164E1F009D4DAF /* CoreStore in Frameworks */, E10EAA47277BB670000269ED /* JellyfinAPI in Frameworks */, @@ -1757,7 +1757,6 @@ 535870902669D7A800D05A09 /* Introspect */, 53ABFDEC26799D7700886593 /* ActivityIndicator */, 536D3D83267BEA550004248C /* ParallaxView */, - 53649AAE269CFAF600A2D8B7 /* Puppy */, 6220D0C826D63F3700B8E046 /* Stinsen */, E13DD3CC27164CA7009D4DAF /* CoreStore */, E12186DD2718F1C50010884C /* Defaults */, @@ -1767,6 +1766,7 @@ E1AE8E7D2789136D00FBDDAA /* Nuke */, E11D83AE278FA998006E9776 /* NukeUI */, E1002B6A2793E36600E47059 /* Algorithms */, + E1347DB5279E3CA500BC6161 /* Puppy */, ); productName = "JellyfinPlayer tvOS"; productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */; @@ -1795,7 +1795,6 @@ packageProductDependencies = ( 53352570265EA0A0006CCA86 /* Introspect */, 625CB5792678C4A400530A6E /* ActivityIndicator */, - 53649AAC269CFAEA00A2D8B7 /* Puppy */, 62C29E9B26D0FE4200C1D2E7 /* Stinsen */, E13DD3C52716499E009D4DAF /* CoreStore */, E13DD3D227168E65009D4DAF /* Defaults */, @@ -1807,6 +1806,7 @@ E1AE8E7B2789135A00FBDDAA /* Nuke */, E1361DA6278FA7A300BEC523 /* NukeUI */, E1002B672793CFBA00E47059 /* Algorithms */, + E1347DB1279E3C6200BC6161 /* Puppy */, ); productName = JellyfinPlayer; productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */; @@ -1828,11 +1828,11 @@ name = "Swiftfin Widget"; packageProductDependencies = ( 536D3D7C267BD5F90004248C /* ActivityIndicator */, - 53649AB4269D423A00A2D8B7 /* Puppy */, E13DD3CE27164E1F009D4DAF /* CoreStore */, E13DD3DC27175CE3009D4DAF /* Defaults */, E10EAA46277BB670000269ED /* JellyfinAPI */, E1D7E5A727892566009D0EF7 /* Nuke */, + E1347DB3279E3C9E00BC6161 /* Puppy */, ); productName = WidgetExtensionExtension; productReference = 628B95202670CABD0091AF3B /* Swiftfin Widget.appex */; @@ -1889,7 +1889,6 @@ 5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */, 536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */, - 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */, 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */, E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */, E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */, @@ -1901,6 +1900,7 @@ E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */, E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */, E1002B662793CFBA00E47059 /* XCRemoteSwiftPackageReference "swift-algorithms" */, + E1347DB0279E3C6200BC6161 /* XCRemoteSwiftPackageReference "Puppy" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; projectDirPath = ""; @@ -2865,7 +2865,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 70; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2902,7 +2902,7 @@ CURRENT_PROJECT_VERSION = 70; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2933,7 +2933,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 70; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2960,7 +2960,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 70; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -3028,14 +3028,6 @@ minimumVersion = 0.1.3; }; }; - 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/sushichop/Puppy"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.2.0; - }; - }; 536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/PGSSoft/ParallaxView"; @@ -3100,6 +3092,14 @@ minimumVersion = 1.0.0; }; }; + E1347DB0279E3C6200BC6161 /* XCRemoteSwiftPackageReference "Puppy" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/LePips/Puppy"; + requirement = { + branch = main; + kind = branch; + }; + }; E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kean/NukeUI"; @@ -3153,21 +3153,6 @@ package = 5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; productName = Introspect; }; - 53649AAC269CFAEA00A2D8B7 /* Puppy */ = { - isa = XCSwiftPackageProductDependency; - package = 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */; - productName = Puppy; - }; - 53649AAE269CFAF600A2D8B7 /* Puppy */ = { - isa = XCSwiftPackageProductDependency; - package = 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */; - productName = Puppy; - }; - 53649AB4269D423A00A2D8B7 /* Puppy */ = { - isa = XCSwiftPackageProductDependency; - package = 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */; - productName = Puppy; - }; 536D3D7C267BD5F90004248C /* ActivityIndicator */ = { isa = XCSwiftPackageProductDependency; package = 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */; @@ -3238,6 +3223,21 @@ package = E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */; productName = CombineExt; }; + E1347DB1279E3C6200BC6161 /* Puppy */ = { + isa = XCSwiftPackageProductDependency; + package = E1347DB0279E3C6200BC6161 /* XCRemoteSwiftPackageReference "Puppy" */; + productName = Puppy; + }; + E1347DB3279E3C9E00BC6161 /* Puppy */ = { + isa = XCSwiftPackageProductDependency; + package = E1347DB0279E3C6200BC6161 /* XCRemoteSwiftPackageReference "Puppy" */; + productName = Puppy; + }; + E1347DB5279E3CA500BC6161 /* Puppy */ = { + isa = XCSwiftPackageProductDependency; + package = E1347DB0279E3C6200BC6161 /* XCRemoteSwiftPackageReference "Puppy" */; + productName = Puppy; + }; E1361DA6278FA7A300BEC523 /* NukeUI */ = { isa = XCSwiftPackageProductDependency; package = E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */; diff --git a/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved index 74055c43..f63dcf8e 100644 --- a/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -93,11 +93,11 @@ }, { "package": "Puppy", - "repositoryURL": "https://github.com/sushichop/Puppy", + "repositoryURL": "https://github.com/LePips/Puppy", "state": { - "branch": null, - "revision": "95ce04b0e778b8d7c351876bc98bbf68328dfc9b", - "version": "0.3.1" + "branch": "main", + "revision": "c34356e8e3879bb39656edd1b11622497ef6f290", + "version": null } }, { diff --git a/Swiftfin/App/AppDelegate.swift b/Swiftfin/App/AppDelegate.swift index aacfa0b9..1d72db97 100644 --- a/Swiftfin/App/AppDelegate.swift +++ b/Swiftfin/App/AppDelegate.swift @@ -19,6 +19,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { // Lazily initialize datastack _ = SwiftfinStore.dataStack + LogManager.setup() let audioSession = AVAudioSession.sharedInstance() do { diff --git a/Swiftfin/Info.plist b/Swiftfin/Info.plist index dc65aac2..8948aa04 100644 --- a/Swiftfin/Info.plist +++ b/Swiftfin/Info.plist @@ -2,6 +2,10 @@ + LSSupportsOpeningDocumentsInPlace + + UIFileSharingEnabled + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift index 8ef58c0e..ade9fced 100644 --- a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift +++ b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift @@ -58,7 +58,7 @@ struct ItemLandscapeMainView: View { selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel) } else { - LogManager.shared.log.error("Attempted to play item but no playback information available") + LogManager.log.error("Attempted to play item but no playback information available") } } label: { Label(L10n.playFromBeginning, systemImage: "gobackward") diff --git a/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift b/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift index db91e1c6..d8c9bd55 100644 --- a/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift +++ b/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift @@ -117,7 +117,7 @@ struct PortraitHeaderOverlayView: View { if let selectedVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel { itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel) } else { - LogManager.shared.log.error("Attempted to play item but no playback information available") + LogManager.log.error("Attempted to play item but no playback information available") } } label: { HStack { @@ -141,7 +141,7 @@ struct PortraitHeaderOverlayView: View { selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel) } else { - LogManager.shared.log.error("Attempted to play item but no playback information available") + LogManager.log.error("Attempted to play item but no playback information available") } } label: { Label(L10n.playFromBeginning, systemImage: "gobackward") diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index e46f8f78..9c2fe8ed 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -423,11 +423,11 @@ extension VLCPlayerViewController { viewModel = newViewModel if viewModel.streamType == .direct { - LogManager.shared.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? "--")") + LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? "--")") } else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] { - LogManager.shared.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? "--")") + LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? "--")") } else { - LogManager.shared.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? "--")") + LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? "--")") } } From 0e13f9489417a95d6735950cea38625588bdadc6 Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Sun, 27 Mar 2022 03:02:29 +0900 Subject: [PATCH 02/25] update ci.yml --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89e3f81a..3ac899ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,12 +15,12 @@ jobs: - "Swiftfin" - "Swiftfin tvOS" - runs-on: macos-latest + runs-on: macos-12 steps: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest + xcode-version: '13.3' - name: Checkout uses: actions/checkout@v1 From 00769cffda5ff42a8f0e4abbe63158a059a9757e Mon Sep 17 00:00:00 2001 From: Merkapt Date: Sat, 9 Apr 2022 12:52:04 +0000 Subject: [PATCH 03/25] Translated using Weblate (Swedish) Currently translated at 52.0% (100 of 192 strings) Translation: Swiftfin/Swiftfin Translate-URL: https://translate.jellyfin.org/projects/swiftfin/swiftfin/sv/ --- Translations/sv.lproj/Localizable.strings | Bin 4488 -> 7032 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Translations/sv.lproj/Localizable.strings b/Translations/sv.lproj/Localizable.strings index de7cb8034ed5c67666b49bd71919d17547e85c4e..57992ce7962a465e07db4663ac2ebffd41496c01 100644 GIT binary patch delta 1928 zcmZ`)O;1xn6unI>2rX$PVuOuwEWt!fSh^vGpkPw1U?}V@Ek0`7*QR|18#Ud!g;~1t z4;T&q!kxyLs9XF2Zj3uTXQtElKuGiYF>~&@=bn3K`ex?uoey*4`fL2^;qv%S?C^c! zN4%etvUH`5*S7c)$P;NwN2>T%#W!DaqU0`~oU8)3D!j2Sy(+zI%>We@~|RME%050`3pKKQ|e2V!5cS0hV2ShH5r|QL=^gHnch{ zRXxK_7x@DzqP86q9gQqv$5y~vD$?l>VWlCF58^eGvW@pDNcLpOlwUQaZQ(sinm0w! zLWL42wQX#yA(_I%s5;6%GPpW^(*j#y-_{l;3ak8fipqu1lQJH5tPY=-PzP(TL>yYh zx=mzdZ45+|kh%f~hFljB7(j1N{~Wuu!paFH@p%=po&dY`AxRlg6vO6rWm~7x*$J0` zj0OFBB7;UZvCH8JOSI0hnRV>eAdI7<;2u5P#lto!G?F>lIu_a)W+sjZw%U%?GI#=m zaHCs9PinFq2kfRgh|NtE2jom)=K)is6QeVE>(xW-vtd0<6G@#I;uY37r3z^&NP(I& z-oS4Kv}b17+y9u+S7MhBM(1@QmR^i9IAh9)@J%0Sq!Y_A+X8EF`jAythS`3^U19Kl$cB@oPC~F&%`wJ z%jC?Z5Ie}>(E3Y!Qol;(`o3Y>LtGm^*an0uRx$9wV1{d9HHg`k$0jB^V(f~-I#s*M zIXo-SS%v22p!Jc1*oReT%_)m6A#n!=+qpUK6ovanF Date: Tue, 12 Apr 2022 02:00:55 +0900 Subject: [PATCH 04/25] update ci.yml --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ac899ec..12b61e76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,6 @@ jobs: - name: xcodebuild! run: | xcodebuild build -project "Swiftfin.xcodeproj" \ - -resolvePackageDependencies \ -scheme "${{ matrix.scheme }}" \ CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO From cfc6e9a4f70236b3b6ca85d7549128e66d281335 Mon Sep 17 00:00:00 2001 From: Marius Lindvall Date: Mon, 11 Apr 2022 18:07:25 +0000 Subject: [PATCH 05/25] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (192 of 192 strings) Translation: Swiftfin/Swiftfin Translate-URL: https://translate.jellyfin.org/projects/swiftfin/swiftfin/nb_NO/ --- Translations/nb-NO.lproj/Localizable.strings | Bin 4854 -> 12946 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Translations/nb-NO.lproj/Localizable.strings b/Translations/nb-NO.lproj/Localizable.strings index dcbbfbcb1506073aad97bbc4d111f88736b8adec..1bd7e9752ee9c6514277c8d605987722b8bfa7b3 100644 GIT binary patch literal 12946 zcmbuG+iqLe5r+45o`NvypeW!3DUh2KMN1!)D3&F|lpOR@mMB>9tvS=rw0+wb$62GAisy9rN))Raok?2g^%wECX79!2o&KEX@%rzvoO|AZ}dl_-0XH|QCl0LQCl1JvBCG}8IyOb^WlNr=0@x}0K zTmLzh&YPB3vg`Doa_fqeJt=%XNB&q*xR7)#-&Y(C*7|v-dvA0H?1#_y*Q`u^_7o+e z6<7#FT?>EjC2K<*KQt_!h!Ujd+NUkP;OD5Lf-Z3MK!`q(2D$2|VN*F4a()te&c*d7 zak!FIZx@(J`X5@x;&t@A)ZdHY(@=BA*U!WO^ySLOqJA%&u!_4M^gHB|Ygf{QJSS^e z3A)MMzv=gtG{WMy!>%HR=aX+-IUBx{HV=ot>bEsJku>Cuw04#ArT9Tf*6r)lcf;>* z^tqCb*TY!ZwBJ0C(x^wjN_Ti3FY+|3xDxeo7n5g_OGQfm&szlS+r5Pkllx;yB~wSj zO?oT%&*eWVDsd;9&&9hUV51>pE)SwduDa5?!$x1JC*l#;dkyn?YBrJQ_lBp6(N5t^ zRlFL|x>EY8W8liu^T%^|jn!T3a=mydGDmA>8(K2bea1}&Vgu1Sg{mFeI)W7RA z2%ckcI8>LsX!6hXxn}1?5&CBMouc%eXdVuKY@b6nx*qkS_V#U|63dKn+vvVbZP*vq z-sty%w3)O@0FF}6vEn~(y0DT6k&$l~-gJMgzFg&L#Ky|s;tU;GNGwxg^=C*(PUvlF zqV?g%t3kOfkNnd4q^|IULv0K%UNq~dEM#8w#>>T>xE_9_K1`L#M>m;=*H2rwx|B6l z*OL}eGR<+c&V27cM8(z3`{-LzL_Lc-8RJ! zJK$<0nTE_lkFRb#1eeG`@cBXhr)SSw{xa`XuhC^a*%<|vka-C2k-A=Hsi!t#`c)^@ zaokh9(-%Y+ys##!SR$5t5!cfe&#C5CI@ween0{P6@IqJ7CHP<){UfpAs)-%D;&ykP z-4cZ-cB9(8y|1y!FD+VL7y3g>9h-F%aMhoc?;3?Ym~(G$0s8{SqDpmz!~-euq~VLX ztoEeGTKz_P&@=H)Rx&CISBS{n-kCJtJ8re>FfsL$&JV5EoZe_wf*&%M_c|MrhekeIckNw-kIoJ>9NjJ-;ub|OVeBCcA$89Mxb-h1!HEY zw(K^WW5R=rcT2b96z)i;-&1c>$5-ln-)q*H=;xoRX5mRRq0XTDd#${E>h@&ky@BV$ zPc7@yHuJ)R$S2pUWvR<^-60cf2XS4u-+Ul0=vMjts5NxBYt>9NzMJPNwR=y`_eINq z_pJiJ`EH&}WcC#K(R(p8y^BMxpfb^;4qI0Mo2%3Go@5zqt{AS_iE8Gf$lvw8&S`#i zM~{bHzf$8T!&p(_?f6at%BB!iGc_qabz9E&)3?uGm>}4b&z0BUgK8f&$=S+2hPuHXv+ij_ zBM{4!x4M2?cVa?VzxF^Jjo_5JC2gpD!T)D{r|v#pAmXF`_S4}H;=wrvGZ~1Ay12~2 z_T07ps6~W%#g#QUMCCTdm`3p!Q3+qw$(C7oq_e4!P9&1j(PxI|JZ?jl)3NdS*+BK5 z>T2?f`&4eum9TBO_Y*C=pScWDF2tYR?4~qi+N@f>l1w~h3RV(T_MxC5Dr$Co`2D@{ zAdYlUbmj!7ako->a`$NA&xe$r<^p!kVoQk&B5=dyCvt`SGyv*(aW<)mi2 zOM>%$V)?#JEZ)a_61^)Qoq_2;qIu9PfFE7!rR0!H(9kPVmu*?|Vfa_GHaOuspLX`U zWLHrt4_>V~{k-`c{$x+aitc)@`azaox4l8_yp@CfOErm#Fw)9XSwyFHcE)K#m4KbZ z4ce38ACjK7xkki}D?H;ltkQYviu;iV8UBqgk71lh$26=#XZve|u^ z9Ws#!8IH3vFyy=v1z7H*z0so0DI8~o=6xi+KtDXmuHYJZ_dAVB=T1A)Gw!1m z7M$uQB3h$HKO-ACX|J=Wt>HJAPTh7|_u!n_buQ%K_x_}3yR9{<>;a#RbFKw`=zgBx zC+%}DXH(sCd}GC_EJQm_{K>;Om#J}mC^YtGGN8qhjh$GP)_k+HBIqMlc&vJset;I) zNt_L~gS`sT_9R8qVJ1WS?$#*M6oVJtm?p@h1y7uR@lw%8Z=S$X(~( zyCygGo%Re-xNLbupU71cePinI)n!`pGUpysf|c+epO0J2;GVRvC5`iMDnroDg-oy) zy)pPwr>S3+Z>8<^HFee2sHeZNk4zodH!@8!CvrWpNq(bA(r$}3uO=l?#*YVucC$r- zc#Oo^_j87IWO%NERiL8Ahzi0~=((p_%(D~ktozQFy_GIk5lI&$>z1>e_3%%dk%QOi zO+7tN@965ae;Vmj8Z>3vV4kQ__QZ+%z6($KaCrE(^{ZLAo=>0K!Iixyxx{H0QOVpR z8`&51IAIneLjPOd5xJ|y>GV78Q9XSg>ioH{z|=l`=RE`+#!q^veE5Wp6B>jidFq__ zqdHq=P!SHBMlC2jpcZXNr3xAH+Od*X_w#*C%|$&Vj$VHuw6en|Jl$A0~62$&rm9V0{JV zw08aZFxoiJE2k>0U%8H&o&A+0lkJXn&5~8w(c3>QDu;VD2pLIB@LH>7@4bnr`ni6c z5Ah?_+!;@1rrzF-a%ut(7f>z9g)E%DpQo)Hncf3cQYlp$S?9N_sP~X^~UnGZF^B1*)`POpr zi96QGqN-$NjcMS%lBtS#ny7rfl;u;HI)kLgp~5OxKrn_x>IZ19mU6MiLy^tMKj(*S#;I zCe+FAna~R+dK$7ZpC46pR1a5tWTi#-KyKQfCm5j{nSxaBVew|I6uEMzqay}ck%E`A zk4{E?$+fSWgynNNDpvJDym=CH@A7+4+b#CF|E~UcDhZx;`?_C!Lw4HlJS|Pk^>2MG zPdebu6Q<8ja~+Wc+4Us;MY(1>K&Vj>X8^r#m>lx{vX5-z^87Vv`uEcmxk$t-GM?!> z_)eYU*~x8v*ZSYqZ<%{{7Bv5*0& zLFf>3l;1L8BDib1{X9MYf9=vgOxYl{);}_dcYJu0F(`=ub;sVXxlLs6et)WVfUzXa z)}CW!N1!8mBI)%dsomyh4Xv9eFnu$-AngADymf9CF?xcZ*yN+$2*klozG5yC7C(hD^KNS6T3%9;F> fI~#6QIL|^R7-#Z-zLPNa=JWi~2nQJo9%cjp0emh= From 889ad9af7994a85333c31146c1cf00814b9e875b Mon Sep 17 00:00:00 2001 From: CullieM Date: Fri, 15 Apr 2022 10:46:21 +1000 Subject: [PATCH 06/25] [Fixed] nil check on remaining progress --- Swiftfin tvOS/Components/MediaPlayButtonRowView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Swiftfin tvOS/Components/MediaPlayButtonRowView.swift b/Swiftfin tvOS/Components/MediaPlayButtonRowView.swift index 36378e36..96c412cb 100644 --- a/Swiftfin tvOS/Components/MediaPlayButtonRowView.swift +++ b/Swiftfin tvOS/Components/MediaPlayButtonRowView.swift @@ -25,7 +25,7 @@ struct MediaPlayButtonRowView: View { MediaViewActionButton(icon: "play.fill", scrollView: $wrappedScrollView) } - Text(viewModel.item.getItemProgressString() != "" ? "\(viewModel.item.getItemProgressString() ?? "") left" : L10n.play) + Text((viewModel.item.getItemProgressString() != nil) ? "\(viewModel.item.getItemProgressString() ?? "") left" : L10n.play) .font(.caption) } VStack { From 615e71c334ef83c9148409fd64b498af38095d88 Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Sat, 16 Apr 2022 18:55:16 +0900 Subject: [PATCH 07/25] Replace swipe gestures for jump with doubleTapGesture run Swiftformat . --- .../CinematicNextUpCardView.swift | 4 +- .../CinematicResumeCardView.swift | 4 +- .../Overlays/tvOSLiveTVOverlay.swift | 12 ++-- .../VideoPlayer/Overlays/tvOSVLCOverlay.swift | 12 ++-- Swiftfin.xcodeproj/project.pbxproj | 4 +- Swiftfin/Components/PortraitHStackView.swift | 6 +- Swiftfin/Components/PortraitItemButton.swift | 6 +- Swiftfin/Views/ContinueWatchingView.swift | 4 +- Swiftfin/Views/ItemView/ItemViewBody.swift | 2 +- .../Overlays/VLCPlayerOverlayView.swift | 20 +++--- .../VideoPlayer/VLCPlayerViewController.swift | 71 ++++++++++++------- 11 files changed, 81 insertions(+), 64 deletions(-) diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift index fa95e274..3cab0897 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift @@ -28,13 +28,13 @@ struct CinematicNextUpCardView: View { item.getSeriesThumbImage(maxWidth: 350), item.getSeriesBackdropImage(maxWidth: 350), ]) - .frame(width: 350, height: 210) + .frame(width: 350, height: 210) } else { ImageView([ .init(url: item.getThumbImage(maxWidth: 350)), .init(url: item.getBackdropImage(maxWidth: 350), blurHash: item.getBackdropImageBlurHash()), ]) - .frame(width: 350, height: 210) + .frame(width: 350, height: 210) } LinearGradient(colors: [.clear, .black], diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift index 8aa188c5..52b3cf88 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift @@ -29,13 +29,13 @@ struct CinematicResumeCardView: View { item.getSeriesThumbImage(maxWidth: 350), item.getSeriesBackdropImage(maxWidth: 350), ]) - .frame(width: 350, height: 210) + .frame(width: 350, height: 210) } else { ImageView([ .init(url: item.getThumbImage(maxWidth: 350)), .init(url: item.getBackdropImage(maxWidth: 350), blurHash: item.getBackdropImageBlurHash()), ]) - .frame(width: 350, height: 210) + .frame(width: 350, height: 210) } LinearGradient(colors: [.clear, .black], diff --git a/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSLiveTVOverlay.swift b/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSLiveTVOverlay.swift index 1a0f3814..dace960b 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSLiveTVOverlay.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSLiveTVOverlay.swift @@ -62,18 +62,18 @@ struct tvOSLiveTVOverlay: View { SFSymbolButton(systemName: "chevron.left.circle", action: { viewModel.playerOverlayDelegate?.didSelectPlayPreviousItem() }) - .frame(maxWidth: 30, maxHeight: 30) - .disabled(viewModel.previousItemVideoPlayerViewModel == nil) - .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) + .frame(maxWidth: 30, maxHeight: 30) + .disabled(viewModel.previousItemVideoPlayerViewModel == nil) + .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) } if viewModel.shouldShowPlayNextItem { SFSymbolButton(systemName: "chevron.right.circle", action: { viewModel.playerOverlayDelegate?.didSelectPlayNextItem() }) - .frame(maxWidth: 30, maxHeight: 30) - .disabled(viewModel.nextItemVideoPlayerViewModel == nil) - .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) + .frame(maxWidth: 30, maxHeight: 30) + .disabled(viewModel.nextItemVideoPlayerViewModel == nil) + .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) } if viewModel.shouldShowAutoPlay { diff --git a/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSVLCOverlay.swift b/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSVLCOverlay.swift index 20db21e6..aa480d1a 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSVLCOverlay.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSVLCOverlay.swift @@ -62,18 +62,18 @@ struct tvOSVLCOverlay: View { SFSymbolButton(systemName: "chevron.left.circle", action: { viewModel.playerOverlayDelegate?.didSelectPlayPreviousItem() }) - .frame(maxWidth: 30, maxHeight: 30) - .disabled(viewModel.previousItemVideoPlayerViewModel == nil) - .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) + .frame(maxWidth: 30, maxHeight: 30) + .disabled(viewModel.previousItemVideoPlayerViewModel == nil) + .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) } if viewModel.shouldShowPlayNextItem { SFSymbolButton(systemName: "chevron.right.circle", action: { viewModel.playerOverlayDelegate?.didSelectPlayNextItem() }) - .frame(maxWidth: 30, maxHeight: 30) - .disabled(viewModel.nextItemVideoPlayerViewModel == nil) - .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) + .frame(maxWidth: 30, maxHeight: 30) + .disabled(viewModel.nextItemVideoPlayerViewModel == nil) + .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) } if viewModel.shouldShowAutoPlay { diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index b945bfaa..e37de740 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -2951,7 +2951,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 70; - DEVELOPMENT_TEAM = 4BHXT8RHFR; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2977,7 +2977,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 70; - DEVELOPMENT_TEAM = 4BHXT8RHFR; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Swiftfin/Components/PortraitHStackView.swift b/Swiftfin/Components/PortraitHStackView.swift index a9aa57c0..79261be4 100644 --- a/Swiftfin/Components/PortraitHStackView.swift +++ b/Swiftfin/Components/PortraitHStackView.swift @@ -48,9 +48,9 @@ struct PortraitImageHStackView: View { failureView: { InitialFailureView(item.failureInitials) }) - .portraitPoster(width: maxWidth) - .shadow(radius: 4, y: 2) - .accessibilityIgnoresInvertColors() + .portraitPoster(width: maxWidth) + .shadow(radius: 4, y: 2) + .accessibilityIgnoresInvertColors() if item.showTitle { Text(item.title) diff --git a/Swiftfin/Views/ContinueWatchingView.swift b/Swiftfin/Views/ContinueWatchingView.swift index e4f55910..505e187f 100644 --- a/Swiftfin/Views/ContinueWatchingView.swift +++ b/Swiftfin/Views/ContinueWatchingView.swift @@ -33,13 +33,13 @@ struct ContinueWatchingView: View { item.getSeriesThumbImage(maxWidth: 320), item.getSeriesBackdropImage(maxWidth: 320), ]) - .frame(width: 320, height: 180) + .frame(width: 320, height: 180) } else { ImageView(sources: [ item.getThumbImage(maxWidth: 320), item.getBackdropImage(maxWidth: 320), ]) - .frame(width: 320, height: 180) + .frame(width: 320, height: 180) } } .accessibilityIgnoresInvertColors() diff --git a/Swiftfin/Views/ItemView/ItemViewBody.swift b/Swiftfin/Views/ItemView/ItemViewBody.swift index 55dfc8a5..5e9b959d 100644 --- a/Swiftfin/Views/ItemView/ItemViewBody.swift +++ b/Swiftfin/Views/ItemView/ItemViewBody.swift @@ -69,7 +69,7 @@ struct ItemViewBody: View { selectedAction: { genre in itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title)) }) - .padding(.bottom) + .padding(.bottom) } // MARK: Studios diff --git a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift index 9bb57530..5530e798 100644 --- a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift +++ b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift @@ -375,16 +375,16 @@ struct VLCPlayerOverlayView: View { ValueSlider(value: $viewModel.sliderPercentage, onEditingChanged: { editing in viewModel.sliderIsScrubbing = editing }) - .valueSliderStyle(HorizontalValueSliderStyle(track: - HorizontalValueTrack(view: - Capsule().foregroundColor(.purple)) - .background(Capsule().foregroundColor(Color.gray.opacity(0.25))) - .frame(height: 4), - thumb: Circle().foregroundColor(.purple), - thumbSize: CGSize.Circle(radius: viewModel.sliderIsScrubbing ? 20 : 15), - thumbInteractiveSize: CGSize.Circle(radius: 40), - options: .defaultOptions)) - .frame(maxHeight: 50) + .valueSliderStyle(HorizontalValueSliderStyle(track: + HorizontalValueTrack(view: + Capsule().foregroundColor(.purple)) + .background(Capsule().foregroundColor(Color.gray.opacity(0.25))) + .frame(height: 4), + thumb: Circle().foregroundColor(.purple), + thumbSize: CGSize.Circle(radius: viewModel.sliderIsScrubbing ? 20 : 15), + thumbInteractiveSize: CGSize.Circle(radius: 40), + options: .defaultOptions)) + .frame(maxHeight: 50) Text(viewModel.rightLabelText) .font(.system(size: 18, weight: .semibold, design: .default)) diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index 6f7e2173..f2eda1ce 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -60,23 +60,33 @@ class VLCPlayerViewController: UIViewController { UIKeyCommand(title: L10n.playAndPause, action: #selector(didSelectMain), input: " "), UIKeyCommand(title: L10n.jumpForward, action: #selector(didSelectForward), input: UIKeyCommand.inputRightArrow), UIKeyCommand(title: L10n.jumpBackward, action: #selector(didSelectBackward), input: UIKeyCommand.inputLeftArrow), - UIKeyCommand(title: L10n.nextItem, action: #selector(didSelectPlayNextItem), input: UIKeyCommand.inputRightArrow, + UIKeyCommand(title: L10n.nextItem, + action: #selector(didSelectPlayNextItem), + input: UIKeyCommand.inputRightArrow, modifierFlags: .command), - UIKeyCommand(title: L10n.previousItem, action: #selector(didSelectPlayPreviousItem), input: UIKeyCommand.inputLeftArrow, + UIKeyCommand(title: L10n.previousItem, + action: #selector(didSelectPlayPreviousItem), + input: UIKeyCommand.inputLeftArrow, modifierFlags: .command), UIKeyCommand(title: L10n.close, action: #selector(didSelectClose), input: UIKeyCommand.inputEscape), ] if let previous = viewModel.playbackSpeed.previous { commands.append(.init(title: "\(L10n.playbackSpeed) \(previous.displayTitle)", - action: #selector(didSelectPreviousPlaybackSpeed), input: "[", modifierFlags: .command)) + action: #selector(didSelectPreviousPlaybackSpeed), + input: "[", + modifierFlags: .command)) } if let next = viewModel.playbackSpeed.next { - commands.append(.init(title: "\(L10n.playbackSpeed) \(next.displayTitle)", action: #selector(didSelectNextPlaybackSpeed), - input: "]", modifierFlags: .command)) + commands.append(.init(title: "\(L10n.playbackSpeed) \(next.displayTitle)", + action: #selector(didSelectNextPlaybackSpeed), + input: "]", + modifierFlags: .command)) } if viewModel.playbackSpeed != .one { commands.append(.init(title: "\(L10n.playbackSpeed) \(PlaybackSpeed.one.displayTitle)", - action: #selector(didSelectNormalPlaybackSpeed), input: "\\", modifierFlags: .command)) + action: #selector(didSelectNormalPlaybackSpeed), + input: "\\", + modifierFlags: .command)) } commands.forEach { $0.wantsPriorityOverSystemBehavior = true } return commands @@ -148,12 +158,18 @@ class VLCPlayerViewController: UIViewController { refreshJumpForwardOverlayView(with: viewModel.jumpForwardLength) let defaultNotificationCenter = NotificationCenter.default - defaultNotificationCenter.addObserver(self, selector: #selector(appWillTerminate), name: UIApplication.willTerminateNotification, + defaultNotificationCenter.addObserver(self, + selector: #selector(appWillTerminate), + name: UIApplication.willTerminateNotification, + object: nil) + defaultNotificationCenter.addObserver(self, + selector: #selector(appWillResignActive), + name: UIApplication.willResignActiveNotification, + object: nil) + defaultNotificationCenter.addObserver(self, + selector: #selector(appWillResignActive), + name: UIApplication.didEnterBackgroundNotification, object: nil) - defaultNotificationCenter.addObserver(self, selector: #selector(appWillResignActive), - name: UIApplication.willResignActiveNotification, object: nil) - defaultNotificationCenter.addObserver(self, selector: #selector(appWillResignActive), - name: UIApplication.didEnterBackgroundNotification, object: nil) } @objc @@ -205,11 +221,8 @@ class VLCPlayerViewController: UIViewController { let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap)) - let rightSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(didRightSwipe)) - rightSwipeGesture.direction = .right - - let leftSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(didLeftSwipe)) - leftSwipeGesture.direction = .left + let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(didDoubleTap)) + doubleTapGesture.numberOfTapsRequired = 2 let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(didPinch(_:))) @@ -219,8 +232,10 @@ class VLCPlayerViewController: UIViewController { view.addGestureRecognizer(pinchGesture) if viewModel.jumpGesturesEnabled { - view.addGestureRecognizer(rightSwipeGesture) - view.addGestureRecognizer(leftSwipeGesture) + view.addGestureRecognizer(doubleTapGesture) + singleTapGesture.require(toFail: doubleTapGesture) + singleTapGesture.delaysTouchesBegan = true + doubleTapGesture.delaysTouchesBegan = true } if viewModel.systemControlGesturesEnabled { @@ -246,13 +261,12 @@ class VLCPlayerViewController: UIViewController { } @objc - private func didRightSwipe() { - didSelectForward() - } - - @objc - private func didLeftSwipe() { - didSelectBackward() + private func didDoubleTap(_ gestureRecognizer: UITapGestureRecognizer) { + if gestureRecognizer.location(in: mainGestureView).x > (mainGestureView.frame.width / 2) { + didSelectForward() + } else { + didSelectBackward() + } } @objc @@ -736,8 +750,11 @@ extension VLCPlayerViewController { extension VLCPlayerViewController { private func restartOverlayDismissTimer(interval: Double = 3) { overlayDismissTimer?.invalidate() - overlayDismissTimer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(dismissTimerFired), - userInfo: nil, repeats: false) + overlayDismissTimer = Timer.scheduledTimer(timeInterval: interval, + target: self, + selector: #selector(dismissTimerFired), + userInfo: nil, + repeats: false) } @objc From f98aa8a396062b7dd4e4471aa8d5ac8491653ab3 Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Sat, 16 Apr 2022 19:30:06 +0900 Subject: [PATCH 08/25] Support Gestures Lock --- .../VideoPlayer/VLCPlayerViewController.swift | 83 ++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index f2eda1ce..5f8a3f96 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -28,6 +28,7 @@ class VLCPlayerViewController: UIViewController { private var viewModelListeners = Set() private var overlayDismissTimer: Timer? private var isScreenFilled: Bool = false + private var isGesturesLocked = false private var pinchScale: CGFloat = 1 private var currentPlayerTicks: Int64 { @@ -49,6 +50,7 @@ class VLCPlayerViewController: UIViewController { private lazy var videoContentView = makeVideoContentView() private lazy var mainGestureView = makeMainGestureView() private lazy var systemControlOverlayLabel = makeSystemControlOverlayLabel() + private lazy var lockedOverlayView = makeGestureLockedOverlayView() private var currentOverlayHostingController: UIHostingController? private var currentChapterOverlayHostingController: UIHostingController? private var currentJumpBackwardOverlayView: UIImageView? @@ -112,6 +114,7 @@ class VLCPlayerViewController: UIViewController { view.addSubview(videoContentView) view.addSubview(mainGestureView) view.addSubview(systemControlOverlayLabel) + view.addSubview(lockedOverlayView) } private func setupConstraints() { @@ -131,6 +134,12 @@ class VLCPlayerViewController: UIViewController { systemControlOverlayLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), systemControlOverlayLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor), ]) + NSLayoutConstraint.activate([ + lockedOverlayView.topAnchor.constraint(equalTo: view.topAnchor), + lockedOverlayView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + lockedOverlayView.leftAnchor.constraint(equalTo: view.leftAnchor), + lockedOverlayView.rightAnchor.constraint(equalTo: view.rightAnchor), + ]) } // MARK: viewWillDisappear @@ -228,8 +237,11 @@ class VLCPlayerViewController: UIViewController { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(_:))) + let longPeessGesture = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress)) + view.addGestureRecognizer(singleTapGesture) view.addGestureRecognizer(pinchGesture) + view.addGestureRecognizer(longPeessGesture) if viewModel.jumpGesturesEnabled { view.addGestureRecognizer(doubleTapGesture) @@ -255,6 +267,33 @@ class VLCPlayerViewController: UIViewController { return label } + // MARK: GestureLockedOverlayView + + private func makeGestureLockedOverlayView() -> UIView { + let backgroundView = UIView() + backgroundView.alpha = 0 + backgroundView.translatesAutoresizingMaskIntoConstraints = false + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + self?.isGesturesLocked = false + self?.hideLockedOverlay() + })) + button.translatesAutoresizingMaskIntoConstraints = false + button.setImage(UIImage(systemName: "lock.open", withConfiguration: UIImage.SymbolConfiguration(pointSize: 48))? + .withTintColor(.white), + for: .normal) + backgroundView.addSubview(button) + + NSLayoutConstraint.activate([ + button.centerXAnchor.constraint(equalTo: backgroundView.centerXAnchor), + button.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor), + ]) + + let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap)) + backgroundView.addGestureRecognizer(singleTapGesture) + + return backgroundView + } + @objc private func didTap() { didGenerallyTap() @@ -269,6 +308,13 @@ class VLCPlayerViewController: UIViewController { } } + @objc + private func didLongPress() { + guard !isGesturesLocked else { return } + isGesturesLocked = true + didGenerallyTap() + } + @objc private func didPinch(_ gestureRecognizer: UIPinchGestureRecognizer) { if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { @@ -630,6 +676,36 @@ extension VLCPlayerViewController { } } +// MARK: Show/Hide Locked Overlay + +extension VLCPlayerViewController { + private func showLockedOverlay() { + guard lockedOverlayView.alpha != 1 else { return } + + UIView.animate(withDuration: 0.2) { + self.lockedOverlayView.alpha = 1 + } + } + + private func hideLockedOverlay() { + guard !UIAccessibility.isVoiceOverRunning else { return } + + guard lockedOverlayView.alpha != 0 else { return } + + UIView.animate(withDuration: 0.2) { + self.lockedOverlayView.alpha = 0 + } + } + + private func toggleLockedOverlay() { + if lockedOverlayView.alpha < 1 { + showLockedOverlay() + } else { + hideLockedOverlay() + } + } +} + // MARK: Show/Hide System Control extension VLCPlayerViewController { @@ -760,6 +836,7 @@ extension VLCPlayerViewController { @objc private func dismissTimerFired() { hideOverlay() + hideLockedOverlay() } private func stopOverlayDismissTimer() { @@ -922,7 +999,11 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { } func didGenerallyTap() { - toggleOverlay() + if isGesturesLocked { + toggleLockedOverlay() + } else { + toggleOverlay() + } restartOverlayDismissTimer(interval: 5) } From 87825c8299b75cc2e947964284511c0bcabaa46d Mon Sep 17 00:00:00 2001 From: Merkapt Date: Mon, 18 Apr 2022 17:49:04 +0000 Subject: [PATCH 09/25] Translated using Weblate (Swedish) Currently translated at 57.8% (111 of 192 strings) Translation: Swiftfin/Swiftfin Translate-URL: https://translate.jellyfin.org/projects/swiftfin/swiftfin/sv/ --- Translations/sv.lproj/Localizable.strings | Bin 7032 -> 7614 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Translations/sv.lproj/Localizable.strings b/Translations/sv.lproj/Localizable.strings index 57992ce7962a465e07db4663ac2ebffd41496c01..1db4b11a16d42a88666e5f172627d1a628a2575a 100644 GIT binary patch delta 487 zcmZ9JF;2rk5JkTw0thRn0U?nlq@bjrpou6@5E4=nqy*>#c94$&)Agp#tdzy zkRaS|Ub0d#PLV;$&&I(#{>@}~wswD4ityxUG7P8M|!l@<_f}^#wUGdaNgS^BhNHXFLy<^+jbxrTeBf Oj`=aSte8Ey*De8DYE?r3 delta 15 WcmdmI{ljbnpY-HO0wR-@q{9F+G6iq| From c2151ff6b1269fc0dd7d36f20bd9cc65c7951ee3 Mon Sep 17 00:00:00 2001 From: James Kerber Date: Mon, 25 Apr 2022 00:36:08 +0000 Subject: [PATCH 10/25] Translated using Weblate (Polish) Currently translated at 98.4% (189 of 192 strings) Translation: Swiftfin/Swiftfin Translate-URL: https://translate.jellyfin.org/projects/swiftfin/swiftfin/pl/ --- Translations/pl.lproj/Localizable.strings | Bin 12934 -> 13122 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Translations/pl.lproj/Localizable.strings b/Translations/pl.lproj/Localizable.strings index e5cb7e6333629c82ddaf028f7e2388fd4b6a941a..c1f60cd43f9ceb45bc915b4514a0a749e7897af4 100644 GIT binary patch delta 262 zcmYL@zY2m-7{!mZm_!(X5ENXb7if+ahn9u{sb(`H`=f@2b!|?-efhjfMFJ+&J5j1LyUFl31AxQ~q!Jk!;Y(%gW shy58`n<60h%5Y`$o Date: Sun, 24 Apr 2022 14:46:49 +0000 Subject: [PATCH 11/25] Translated using Weblate (Polish) Currently translated at 98.4% (189 of 192 strings) Translation: Swiftfin/Swiftfin Translate-URL: https://translate.jellyfin.org/projects/swiftfin/swiftfin/pl/ --- Translations/pl.lproj/Localizable.strings | Bin 13122 -> 13328 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Translations/pl.lproj/Localizable.strings b/Translations/pl.lproj/Localizable.strings index c1f60cd43f9ceb45bc915b4514a0a749e7897af4..fb6a5947cf1902c159ef5b96ca0b6b7c0b47aa76 100644 GIT binary patch delta 215 zcmZ9Gu?@m76htotO{R#v0wqe51wz3BRuaIN#0VT&qG1I_VFo7S86<>|_5IKP?w{|u zz3(5*@@6#3>B4cEw3AV#nlfs1mX+76gBC@nf!RtW_XLhg3MjlbY>rHNwP@py!y5hy t40Fg&xEC-8CC2~& delta 7 OcmbP`aVTwrlQ94ekOM{l From 9d2a3f342e26af77670b7d701f101fbb7686c707 Mon Sep 17 00:00:00 2001 From: James Kerber Date: Mon, 25 Apr 2022 00:28:36 +0000 Subject: [PATCH 12/25] Translated using Weblate (Swedish) Currently translated at 73.9% (142 of 192 strings) Translation: Swiftfin/Swiftfin Translate-URL: https://translate.jellyfin.org/projects/swiftfin/swiftfin/sv/ --- Translations/sv.lproj/Localizable.strings | Bin 7614 -> 9224 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Translations/sv.lproj/Localizable.strings b/Translations/sv.lproj/Localizable.strings index 1db4b11a16d42a88666e5f172627d1a628a2575a..b60427e214fae6be1b07b6b016e42578c67944e2 100644 GIT binary patch delta 1341 zcma)6Jx?1!5FL<0$Z=MpSg9-v%MxYMgO$RHL}DpHMkW@N2R=E5^PR;V`D(6BN3mUs zNc;nS0e#B!Dd8{B(B!>a@9p9fQi`>FGjHC!`PjYc{CIbH)!b}LBAI-WP)?;SEqRU8 zmafDyf+fKJj(GT;NrbO&vIFE-MD*0|YF+*Hy-qCGlEM-oGC)iw6FC7Ys>Jr;4dpZZ ziJT#(!dW`n}xDkI|AT#`7LnGIN-N zf-5P8m^z#rXnG4L#DLBUuO7Q*<$zDY5EkRSulg1qJ!GSMj?MvhH-{w>%&r?K|BsPS zhgjkv62`D{?gpEpm;kn@BI5&a@q&m{atn85XfRjU$~Z!2n2_CzKxBpN$xvRRC-gG8 z+m#tO5(wmFm{y~5wB7}nIx_6}00|TMQVoKIDJJ(4O@ZazVEe7KZCD~Cb|s~-jUkmQ zQhy6qhE%+OynLaI)OoF``Y&FsIY4!{(ojEZ&zm#3!47Z}j8qqOUTgG>otKT5GI8W| z9h=^o6qZBeo#5!9mEzUY<@Mb~{{YDEP+6knBg)|RYxR6wxM>N#$#+uf@;})vsr($7~*X?DL?TW-j`$^rsjacYqzU`@stMG`Q{W7OME5ndN2G F{{UZI>U;nI delta 7 OcmeD1*k`?ApDX|kx&u`J From 365520dc39276868fca2461fbe442b3100f5fa52 Mon Sep 17 00:00:00 2001 From: William Date: Sun, 24 Apr 2022 16:33:53 +0000 Subject: [PATCH 13/25] Translated using Weblate (Swedish) Currently translated at 73.9% (142 of 192 strings) Translation: Swiftfin/Swiftfin Translate-URL: https://translate.jellyfin.org/projects/swiftfin/swiftfin/sv/ --- Translations/sv.lproj/Localizable.strings | Bin 9224 -> 9340 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Translations/sv.lproj/Localizable.strings b/Translations/sv.lproj/Localizable.strings index b60427e214fae6be1b07b6b016e42578c67944e2..84d5087c2695c94db5a6c21a0eb9b08e8d376a68 100644 GIT binary patch delta 108 zcmeD1_~WsGLxo?7p@<=sAq`9xGh|G5 Date: Mon, 25 Apr 2022 00:47:43 +0000 Subject: [PATCH 14/25] Translated using Weblate (Polish) Currently translated at 98.9% (190 of 192 strings) Translation: Swiftfin/Swiftfin Translate-URL: https://translate.jellyfin.org/projects/swiftfin/swiftfin/pl/ --- Translations/pl.lproj/Localizable.strings | Bin 13328 -> 13376 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Translations/pl.lproj/Localizable.strings b/Translations/pl.lproj/Localizable.strings index fb6a5947cf1902c159ef5b96ca0b6b7c0b47aa76..2816410d0fccf54507c56b24f5a270da33da53ff 100644 GIT binary patch delta 266 zcmbP`aUf&EAwia01~rAr1=3oRX9)3Z5_-hQ9mP<@kjhXD#3hqo3(GP(PUaMG6+{wc zC}qfEP?((0BF-2(*^o(g^CXcgEd1d>EmaJaK(d4(n<0O4x#Z)?HYyU6i=;9b!zbUC zs${KVP+$n2oG87VF?e#Lf$(G*84K=mu=-SnWQMBE@iN6M5&=Mai-3ap40#NR3^@#$ zKtn4n4kSw$G8kNd{1TucNaZThJ1!{ppBmyN;Zee6tl>=00lrsfJlflf`PmuAU}^m dl5z6`g(gORSB4y*GjoAB4=7i;IbW$?5C8zFJQ)B0 From 22b432c9b884dd88e5123750a554a07b5d20423e Mon Sep 17 00:00:00 2001 From: Polish Jelly Date: Mon, 25 Apr 2022 00:45:06 +0000 Subject: [PATCH 15/25] Translated using Weblate (Polish) Currently translated at 98.9% (190 of 192 strings) Translation: Swiftfin/Swiftfin Translate-URL: https://translate.jellyfin.org/projects/swiftfin/swiftfin/pl/ --- Translations/pl.lproj/Localizable.strings | Bin 13376 -> 13380 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Translations/pl.lproj/Localizable.strings b/Translations/pl.lproj/Localizable.strings index 2816410d0fccf54507c56b24f5a270da33da53ff..f3d7425ab11232419ecffd2a50aac100d8284966 100644 GIT binary patch delta 36 qcmX?*aU^5ICnNE41_cHuhD?Seh8!T7&rrgU%8<;Ex|!A3nh^loXbJED delta 32 mcmX?-aUf&ECnI4ehD?Seh8!T7&rrgU%8<>FxtY`0nh^lHFbQP< From cf812fd9dd6abca13af44bc3a045a441e7242855 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 28 Apr 2022 14:51:22 -0600 Subject: [PATCH 16/25] redo logging --- .../MainCoordinator/iOSMainCoordinator.swift | 4 +- Shared/Errors/ErrorMessage.swift | 10 +- Shared/Errors/LogConstructor.swift | 19 ---- Shared/Errors/NetworkError.swift | 103 ++++-------------- .../BaseItemDto+VideoPlayerViewModel.swift | 2 +- Shared/Generated/Strings.swift | 2 + Shared/Singleton/LogManager.swift | 71 ++++++------ Shared/Singleton/SessionManager.swift | 4 +- Shared/SwiftfinStore/SwiftfinStore.swift | 6 +- .../ViewModels/ConnectToServerViewModel.swift | 26 ++--- .../ItemViewModel/SeasonItemViewModel.swift | 6 +- Shared/ViewModels/UserSignInViewModel.swift | 4 +- .../VideoPlayerViewModel.swift | 2 +- Shared/ViewModels/ViewModel.swift | 28 ++--- .../CinematicNextUpCardView.swift | 4 +- .../CinematicResumeCardView.swift | 4 +- .../Overlays/tvOSLiveTVOverlay.swift | 12 +- .../VideoPlayer/Overlays/tvOSVLCOverlay.swift | 12 +- Swiftfin.xcodeproj/project.pbxproj | 34 +++--- .../xcshareddata/swiftpm/Package.resolved | 22 ++-- Swiftfin/App/AppDelegate.swift | 2 +- Swiftfin/Components/PortraitHStackView.swift | 6 +- Swiftfin/Components/PortraitItemButton.swift | 6 +- Swiftfin/Info.plist | 8 +- Swiftfin/Views/ConnectToServerView.swift | 2 +- Swiftfin/Views/ContinueWatchingView.swift | 4 +- Swiftfin/Views/HomeView.swift | 2 +- Swiftfin/Views/ItemView/ItemViewBody.swift | 2 +- Swiftfin/Views/UserSignInView.swift | 2 +- .../Overlays/VLCPlayerOverlayView.swift | 20 ++-- Translations/en.lproj/Localizable.strings | Bin 12520 -> 12608 bytes 31 files changed, 169 insertions(+), 260 deletions(-) delete mode 100644 Shared/Errors/LogConstructor.swift diff --git a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift index 7db2b6eb..ec07d8e5 100644 --- a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift @@ -59,13 +59,13 @@ final class MainCoordinator: NavigationCoordinatable { @objc func didSignIn() { - LogManager.shared.log.info("Received `didSignIn` from SwiftfinNotificationCenter.") + LogManager.log.info("Received `didSignIn` from SwiftfinNotificationCenter.") root(\.mainTab) } @objc func didSignOut() { - LogManager.shared.log.info("Received `didSignOut` from SwiftfinNotificationCenter.") + LogManager.log.info("Received `didSignOut` from SwiftfinNotificationCenter.") root(\.serverList) } diff --git a/Shared/Errors/ErrorMessage.swift b/Shared/Errors/ErrorMessage.swift index 38cb0d55..e316d0aa 100644 --- a/Shared/Errors/ErrorMessage.swift +++ b/Shared/Errors/ErrorMessage.swift @@ -13,22 +13,20 @@ struct ErrorMessage: Identifiable { let code: Int let title: String - let displayMessage: String - let logConstructor: LogConstructor + let message: String // Chosen value such that if an error has this code, don't show the code to the UI // This was chosen because of its unlikelyhood to ever be used static let noShowErrorCode = -69420 var id: String { - "\(code)\(title)\(logConstructor.message)" + "\(code)\(title)\(message)" } /// If the custom displayMessage is `nil`, it will be set to the given logConstructor's message - init(code: Int, title: String, displayMessage: String?, logConstructor: LogConstructor) { + init(code: Int, title: String, message: String) { self.code = code self.title = title - self.displayMessage = displayMessage ?? logConstructor.message - self.logConstructor = logConstructor + self.message = message } } diff --git a/Shared/Errors/LogConstructor.swift b/Shared/Errors/LogConstructor.swift deleted file mode 100644 index cd59a3bb..00000000 --- a/Shared/Errors/LogConstructor.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Swiftfin is subject to the terms of the Mozilla Public -// License, v2.0. If a copy of the MPL was not distributed with this -// file, you can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2022 Jellyfin & Jellyfin Contributors -// - -import Foundation -import JellyfinAPI - -struct LogConstructor { - var message: String - let tag: String - let level: LogLevel - let function: String - let file: String - let line: UInt -} diff --git a/Shared/Errors/NetworkError.swift b/Shared/Errors/NetworkError.swift index 0930e90a..7778f61a 100644 --- a/Shared/Errors/NetworkError.swift +++ b/Shared/Errors/NetworkError.swift @@ -9,127 +9,75 @@ import Foundation import JellyfinAPI -/** - The implementation of the network errors here are a temporary measure. - It is very repetitive, messy, and doesn't fulfill the entire specification of "error reporting". - - Needs to be replaced - */ - enum NetworkError: Error { /// For the case that the ErrorResponse object has a code of -1 - case URLError(response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) + case URLError(response: ErrorResponse, displayMessage: String?) /// For the case that the ErrorRespones object has a code of -2 - case HTTPURLError(response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) + case HTTPURLError(response: ErrorResponse, displayMessage: String?) /// For the case that the ErrorResponse object has a positive code - case JellyfinError(response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) + case JellyfinError(response: ErrorResponse, displayMessage: String?) var errorMessage: ErrorMessage { switch self { - case let .URLError(response, displayMessage, logConstructor): - return NetworkError.parseURLError(from: response, displayMessage: displayMessage, logConstructor: logConstructor) - case let .HTTPURLError(response, displayMessage, logConstructor): - return NetworkError.parseHTTPURLError(from: response, displayMessage: displayMessage, logConstructor: logConstructor) - case let .JellyfinError(response, displayMessage, logConstructor): - return NetworkError.parseJellyfinError(from: response, displayMessage: displayMessage, logConstructor: logConstructor) + case let .URLError(response, displayMessage): + return NetworkError.parseURLError(from: response, displayMessage: displayMessage) + case let .HTTPURLError(response, displayMessage): + return NetworkError.parseHTTPURLError(from: response, displayMessage: displayMessage) + case let .JellyfinError(response, displayMessage): + return NetworkError.parseJellyfinError(from: response, displayMessage: displayMessage) } } - func logMessage() { - let logConstructor = errorMessage.logConstructor - let logFunction: (@autoclosure () -> String, String, String, String, UInt) -> Void - - switch logConstructor.level { - case .trace: - logFunction = LogManager.log.trace - case .debug: - logFunction = LogManager.log.debug - case .information: - logFunction = LogManager.log.info - case .warning: - logFunction = LogManager.log.warning - case .error: - logFunction = LogManager.log.error - case .critical: - logFunction = LogManager.log.critical - case ._none: - logFunction = LogManager.log.debug - } - - logFunction(logConstructor.message, logConstructor.tag, logConstructor.function, logConstructor.file, logConstructor.line) - } - - private static func parseURLError(from response: ErrorResponse, displayMessage: String?, - logConstructor: LogConstructor) -> ErrorMessage - { - + private static func parseURLError(from response: ErrorResponse, displayMessage: String?) -> ErrorMessage { let errorMessage: ErrorMessage - var logMessage = L10n.unknownError - var logConstructor = logConstructor switch response { case let .error(_, _, _, err): - // These codes are currently referenced from: + // Code references: // https://developer.apple.com/documentation/foundation/1508628-url_loading_system_error_codes switch err._code { case -1001: - logMessage = L10n.networkTimedOut - logConstructor.message = logMessage errorMessage = ErrorMessage(code: err._code, title: L10n.error, - displayMessage: displayMessage, - logConstructor: logConstructor) + message: L10n.networkTimedOut) + case -1003: + errorMessage = ErrorMessage(code: err._code, + title: L10n.error, + message: L10n.unableToFindHost) case -1004: - logMessage = L10n.cannotConnectToHost - logConstructor.message = logMessage errorMessage = ErrorMessage(code: err._code, title: L10n.error, - displayMessage: displayMessage, - logConstructor: logConstructor) + message: L10n.cannotConnectToHost) default: - logConstructor.message = logMessage errorMessage = ErrorMessage(code: err._code, title: L10n.error, - displayMessage: displayMessage, - logConstructor: logConstructor) + message: L10n.unknownError) } } return errorMessage } - private static func parseHTTPURLError(from response: ErrorResponse, displayMessage: String?, - logConstructor: LogConstructor) -> ErrorMessage - { - + private static func parseHTTPURLError(from response: ErrorResponse, displayMessage: String?) -> ErrorMessage { let errorMessage: ErrorMessage - let logMessage = "An HTTP URL error has occurred" - var logConstructor = logConstructor // Not implemented as has not run into one of these errors as time of writing switch response { case .error: - logConstructor.message = logMessage errorMessage = ErrorMessage(code: 0, title: L10n.error, - displayMessage: displayMessage, - logConstructor: logConstructor) + message: "An HTTP URL error has occurred") } return errorMessage } - private static func parseJellyfinError(from response: ErrorResponse, displayMessage: String?, - logConstructor: LogConstructor) -> ErrorMessage - { - + private static func parseJellyfinError(from response: ErrorResponse, displayMessage: String?) -> ErrorMessage { let errorMessage: ErrorMessage - var logMessage = L10n.unknownError - var logConstructor = logConstructor switch response { case let .error(code, _, _, _): @@ -137,18 +85,13 @@ enum NetworkError: Error { // Generic HTTP status codes switch code { case 401: - logMessage = L10n.unauthorizedUser - logConstructor.message = logMessage errorMessage = ErrorMessage(code: code, title: L10n.unauthorized, - displayMessage: displayMessage, - logConstructor: logConstructor) + message: L10n.unauthorizedUser) default: - logConstructor.message = logMessage errorMessage = ErrorMessage(code: code, title: L10n.error, - displayMessage: displayMessage, - logConstructor: logConstructor) + message: L10n.unknownError) } } diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift index 2d19227b..34a0176b 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift @@ -169,7 +169,7 @@ extension BaseItemDto { func createLiveTVVideoPlayerViewModel() -> AnyPublisher<[VideoPlayerViewModel], Error> { - LogManager.shared.log.debug("Creating liveTV video player view model for item: \(id ?? "")") + LogManager.log.debug("Creating liveTV video player view model for item: \(id ?? "")") let builder = DeviceProfileBuilder() // TODO: fix bitrate settings diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index 3f18c5d0..6e011548 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -390,6 +390,8 @@ internal enum L10n { internal static var tvShows: String { return L10n.tr("Localizable", "tvShows") } /// Unable to connect to server internal static var unableToConnectServer: String { return L10n.tr("Localizable", "unableToConnectServer") } + /// Unable to find host + internal static var unableToFindHost: String { return L10n.tr("Localizable", "unableToFindHost") } /// Unaired internal static var unaired: String { return L10n.tr("Localizable", "unaired") } /// Unauthorized diff --git a/Shared/Singleton/LogManager.swift b/Shared/Singleton/LogManager.swift index 205fd686..9b3dbaca 100644 --- a/Shared/Singleton/LogManager.swift +++ b/Shared/Singleton/LogManager.swift @@ -10,44 +10,41 @@ import Foundation import Puppy class LogManager { - - static let log = Puppy() - - static func setup() { - - let logsDirectory = getDocumentsDirectory().appendingPathComponent("logs", isDirectory: true) - - do { - try FileManager.default.createDirectory(atPath: logsDirectory.path, - withIntermediateDirectories: true, - attributes: nil) - } catch { - // logs directory already created - } - - let logFileURL = logsDirectory.appendingPathComponent("swiftfin_log.log") - - let fileRotationLogger = try! FileRotationLogger("org.jellyfin.swiftfin.logger.file-rotation", - fileURL: logFileURL) - fileRotationLogger.suffixExtension = .numbering - fileRotationLogger.maxFileSize = 10 * 1024 - fileRotationLogger.maxArchivedFilesCount = 5 - fileRotationLogger.format = LogFormatter() - - let consoleLogger = ConsoleLogger("org.jellyfin.swiftfin.logger.console") - consoleLogger.format = LogFormatter() - - log.add(fileRotationLogger, withLevel: .debug) - log.add(consoleLogger, withLevel: .debug) - } - - private static func getDocumentsDirectory() -> URL { - // find all possible documents directories for this user - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - // just send back the first one, which ought to be the only one - return paths[0] - } + static let log = Puppy() + + static func setup() { + + let logsDirectory = getDocumentsDirectory().appendingPathComponent("logs", isDirectory: true) + + do { + try FileManager.default.createDirectory(atPath: logsDirectory.path, + withIntermediateDirectories: true, + attributes: nil) + } catch { + // logs directory already created + } + + let logFileURL = logsDirectory.appendingPathComponent("swiftfin_log.log") + + let fileRotationLogger = try! FileRotationLogger("org.jellyfin.swiftfin.logger.file-rotation", + fileURL: logFileURL) + fileRotationLogger.format = LogFormatter() + + let consoleLogger = ConsoleLogger("org.jellyfin.swiftfin.logger.console") + consoleLogger.format = LogFormatter() + + log.add(fileRotationLogger, withLevel: .debug) + log.add(consoleLogger, withLevel: .debug) + } + + private static func getDocumentsDirectory() -> URL { + // find all possible documents directories for this user + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + + // just send back the first one, which ought to be the only one + return paths[0] + } } class LogFormatter: LogFormattable { diff --git a/Shared/Singleton/SessionManager.swift b/Shared/Singleton/SessionManager.swift index 2d3fffd2..5ee49bac 100644 --- a/Shared/Singleton/SessionManager.swift +++ b/Shared/Singleton/SessionManager.swift @@ -104,7 +104,7 @@ final class SessionManager { [Where("id == %@", newServer.id)]) { - throw SwiftfinStore.Errors.existingServer(existingServer.state) + throw SwiftfinStore.Error.existingServer(existingServer.state) } return (newServer, transaction) @@ -210,7 +210,7 @@ final class SessionManager { [Where("id == %@", newUser.id)]) { - throw SwiftfinStore.Errors.existingUser(existingUser.state) + throw SwiftfinStore.Error.existingUser(existingUser.state) } let newAccessToken = transaction.create(Into()) diff --git a/Shared/SwiftfinStore/SwiftfinStore.swift b/Shared/SwiftfinStore/SwiftfinStore.swift index f44b3ab2..578af623 100644 --- a/Shared/SwiftfinStore/SwiftfinStore.swift +++ b/Shared/SwiftfinStore/SwiftfinStore.swift @@ -147,9 +147,9 @@ enum SwiftfinStore { } } - // MARK: Errors + // MARK: Error - enum Errors { + enum Error { case existingServer(State.Server) case existingUser(State.User) } @@ -193,7 +193,7 @@ enum SwiftfinStore { // MARK: LocalizedError -extension SwiftfinStore.Errors: LocalizedError { +extension SwiftfinStore.Error: LocalizedError { var title: String { switch self { diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index be928f17..14d33e6a 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -48,7 +48,7 @@ final class ConnectToServerViewModel: ViewModel { #if targetEnvironment(simulator) var uri = uri - if uri == "localhost" { + if uri == "http://localhost" || uri == "localhost" { uri = "http://localhost:8096" } #endif @@ -71,10 +71,7 @@ final class ConnectToServerViewModel: ViewModel { // a url in the response is the result if a redirect if let newURL = response?.url { if redirectCount > 2 { - self.handleAPIRequestError(displayMessage: L10n.tooManyRedirects, - logLevel: .critical, - tag: "connectToServer", - completion: completion) + self.handleAPIRequestError(displayMessage: L10n.tooManyRedirects, completion: completion) } else { self .connectToServer(uri: newURL.absoluteString @@ -85,21 +82,17 @@ final class ConnectToServerViewModel: ViewModel { self.handleAPIRequestError(completion: completion) } } - case is SwiftfinStore.Errors: - let swiftfinError = error as! SwiftfinStore.Errors + case is SwiftfinStore.Error: + let swiftfinError = error as! SwiftfinStore.Error switch swiftfinError { case let .existingServer(server): self.addServerURIPayload = AddServerURIPayload(server: server, uri: uri) self.backAddServerURIPayload = AddServerURIPayload(server: server, uri: uri) default: - self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, logLevel: .critical, - tag: "connectToServer", - completion: completion) + self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, completion: completion) } default: - self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, logLevel: .critical, - tag: "connectToServer", - completion: completion) + self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, completion: completion) } } }, receiveValue: { server in @@ -128,14 +121,11 @@ final class ConnectToServerViewModel: ViewModel { func addURIToServer(addServerURIPayload: AddServerURIPayload) { SessionManager.main.addURIToServer(server: addServerURIPayload.server, uri: addServerURIPayload.uri) .sink { completion in - self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, logLevel: .critical, tag: "connectToServer", - completion: completion) + self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, completion: completion) } receiveValue: { server in SessionManager.main.setServerCurrentURI(server: server, uri: addServerURIPayload.uri) .sink { completion in - self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, logLevel: .critical, - tag: "connectToServer", - completion: completion) + self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, completion: completion) } receiveValue: { _ in self.router?.dismissCoordinator() } diff --git a/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift b/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift index edb9fb87..3c2da092 100644 --- a/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift @@ -44,7 +44,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager { } private func requestEpisodes() { - LogManager.shared.log + LogManager.log .debug("Getting episodes in season \(item.id!) (\(item.name!)) of show \(item.seriesId!) (\(item.seriesName!))") TvShowsAPI.getEpisodes(seriesId: item.seriesId ?? "", userId: SessionManager.main.currentLogin.user.id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], @@ -55,7 +55,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager { }, receiveValue: { [weak self] response in guard let self = self else { return } self.episodes = response.items ?? [] - LogManager.shared.log.debug("Retrieved \(String(self.episodes.count)) episodes") + LogManager.log.debug("Retrieved \(String(self.episodes.count)) episodes") self.setNextUpInSeason() }) @@ -78,7 +78,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager { !episode.unaired && !episode.missing && episode.seasonId ?? "" == self.item.id! }) { self.playButtonItem = nextUpItem - LogManager.shared.log.debug("Nextup in season \(self.item.id!) (\(self.item.name!)): \(nextUpItem.id!)") + LogManager.log.debug("Nextup in season \(self.item.id!) (\(self.item.name!)): \(nextUpItem.id!)") } if self.playButtonItem == nil && !self.episodes.isEmpty { diff --git a/Shared/ViewModels/UserSignInViewModel.swift b/Shared/ViewModels/UserSignInViewModel.swift index de1d1445..a9b154dc 100644 --- a/Shared/ViewModels/UserSignInViewModel.swift +++ b/Shared/ViewModels/UserSignInViewModel.swift @@ -31,13 +31,11 @@ final class UserSignInViewModel: ViewModel { func login(username: String, password: String) { LogManager.log.debug("Attempting to login to server at \"\(server.currentURI)\"", tag: "login") - LogManager.log.debug("username: \(username), password: \(password)", tag: "login") SessionManager.main.loginUser(server: server, username: username, password: password) .trackActivity(loading) .sink { completion in - self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, logLevel: .critical, tag: "login", - completion: completion) + self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, completion: completion) } receiveValue: { _ in } .store(in: &cancellables) diff --git a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift index 066174fa..677767a0 100644 --- a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift @@ -585,7 +585,7 @@ extension VideoPlayerViewModel { .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { _ in - LogManager.shared.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")") + LogManager.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")") Notifications[.didSendStopReport].post(object: self.item.id) } .store(in: &cancellables) diff --git a/Shared/ViewModels/ViewModel.swift b/Shared/ViewModels/ViewModel.swift index 6e7d5b5f..f96488cd 100644 --- a/Shared/ViewModels/ViewModel.swift +++ b/Shared/ViewModels/ViewModel.swift @@ -25,55 +25,47 @@ class ViewModel: ObservableObject { loading.loading.assign(to: \.isLoading, on: self).store(in: &cancellables) } - func handleAPIRequestError(displayMessage: String? = nil, logLevel: LogLevel = .error, tag: String = "", function: String = #function, - file: String = #file, line: UInt = #line, completion: Subscribers.Completion) - { + func handleAPIRequestError(displayMessage: String? = nil, completion: Subscribers.Completion) { switch completion { case .finished: self.errorMessage = nil case let .failure(error): - let logConstructor = LogConstructor(message: "__NOTHING__", tag: tag, level: logLevel, function: function, file: file, - line: line) - switch error { case is ErrorResponse: let networkError: NetworkError let errorResponse = error as! ErrorResponse + switch errorResponse { case .error(-1, _, _, _): - networkError = .URLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor) + networkError = .URLError(response: errorResponse, displayMessage: displayMessage) // Use the errorResponse description for debugging, rather than the user-facing friendly description which may not be implemented LogManager.log .error("Request failed: URL request failed with error \(networkError.errorMessage.code): \(errorResponse.localizedDescription)") case .error(-2, _, _, _): - networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor) + networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage) LogManager.log .error("Request failed: HTTP URL request failed with description: \(errorResponse.localizedDescription)") default: - networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor) + networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage) // Able to use user-facing friendly description here since just HTTP status codes LogManager.log - .error("Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.logConstructor.message)\n\(error.localizedDescription)") + .error("Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.message)\n\(error.localizedDescription)") } self.errorMessage = networkError.errorMessage - networkError.logMessage() - - case is SwiftfinStore.Errors: - let swiftfinError = error as! SwiftfinStore.Errors + case is SwiftfinStore.Error: + let swiftfinError = error as! SwiftfinStore.Error let errorMessage = ErrorMessage(code: ErrorMessage.noShowErrorCode, title: swiftfinError.title, - displayMessage: swiftfinError.errorDescription ?? "", - logConstructor: logConstructor) + message: swiftfinError.errorDescription ?? "") self.errorMessage = errorMessage LogManager.log.error("Request failed: \(swiftfinError.errorDescription ?? "")") default: let genericErrorMessage = ErrorMessage(code: ErrorMessage.noShowErrorCode, title: "Generic Error", - displayMessage: error.localizedDescription, - logConstructor: logConstructor) + message: error.localizedDescription) self.errorMessage = genericErrorMessage LogManager.log.error("Request failed: Generic error - \(error.localizedDescription)") } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift index fa95e274..3cab0897 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift @@ -28,13 +28,13 @@ struct CinematicNextUpCardView: View { item.getSeriesThumbImage(maxWidth: 350), item.getSeriesBackdropImage(maxWidth: 350), ]) - .frame(width: 350, height: 210) + .frame(width: 350, height: 210) } else { ImageView([ .init(url: item.getThumbImage(maxWidth: 350)), .init(url: item.getBackdropImage(maxWidth: 350), blurHash: item.getBackdropImageBlurHash()), ]) - .frame(width: 350, height: 210) + .frame(width: 350, height: 210) } LinearGradient(colors: [.clear, .black], diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift index 8aa188c5..52b3cf88 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift @@ -29,13 +29,13 @@ struct CinematicResumeCardView: View { item.getSeriesThumbImage(maxWidth: 350), item.getSeriesBackdropImage(maxWidth: 350), ]) - .frame(width: 350, height: 210) + .frame(width: 350, height: 210) } else { ImageView([ .init(url: item.getThumbImage(maxWidth: 350)), .init(url: item.getBackdropImage(maxWidth: 350), blurHash: item.getBackdropImageBlurHash()), ]) - .frame(width: 350, height: 210) + .frame(width: 350, height: 210) } LinearGradient(colors: [.clear, .black], diff --git a/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSLiveTVOverlay.swift b/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSLiveTVOverlay.swift index 1a0f3814..dace960b 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSLiveTVOverlay.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSLiveTVOverlay.swift @@ -62,18 +62,18 @@ struct tvOSLiveTVOverlay: View { SFSymbolButton(systemName: "chevron.left.circle", action: { viewModel.playerOverlayDelegate?.didSelectPlayPreviousItem() }) - .frame(maxWidth: 30, maxHeight: 30) - .disabled(viewModel.previousItemVideoPlayerViewModel == nil) - .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) + .frame(maxWidth: 30, maxHeight: 30) + .disabled(viewModel.previousItemVideoPlayerViewModel == nil) + .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) } if viewModel.shouldShowPlayNextItem { SFSymbolButton(systemName: "chevron.right.circle", action: { viewModel.playerOverlayDelegate?.didSelectPlayNextItem() }) - .frame(maxWidth: 30, maxHeight: 30) - .disabled(viewModel.nextItemVideoPlayerViewModel == nil) - .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) + .frame(maxWidth: 30, maxHeight: 30) + .disabled(viewModel.nextItemVideoPlayerViewModel == nil) + .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) } if viewModel.shouldShowAutoPlay { diff --git a/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSVLCOverlay.swift b/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSVLCOverlay.swift index 20db21e6..aa480d1a 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSVLCOverlay.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/Overlays/tvOSVLCOverlay.swift @@ -62,18 +62,18 @@ struct tvOSVLCOverlay: View { SFSymbolButton(systemName: "chevron.left.circle", action: { viewModel.playerOverlayDelegate?.didSelectPlayPreviousItem() }) - .frame(maxWidth: 30, maxHeight: 30) - .disabled(viewModel.previousItemVideoPlayerViewModel == nil) - .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) + .frame(maxWidth: 30, maxHeight: 30) + .disabled(viewModel.previousItemVideoPlayerViewModel == nil) + .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) } if viewModel.shouldShowPlayNextItem { SFSymbolButton(systemName: "chevron.right.circle", action: { viewModel.playerOverlayDelegate?.didSelectPlayNextItem() }) - .frame(maxWidth: 30, maxHeight: 30) - .disabled(viewModel.nextItemVideoPlayerViewModel == nil) - .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) + .frame(maxWidth: 30, maxHeight: 30) + .disabled(viewModel.nextItemVideoPlayerViewModel == nil) + .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) } if viewModel.shouldShowAutoPlay { diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index b41887e1..537b1932 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -313,6 +313,7 @@ E10EAA51277BBCC4000269ED /* CGSizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */; }; E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */; }; E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */; }; + E1101177281B1E8A006A3584 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1101176281B1E8A006A3584 /* Puppy */; }; E111DE222790BB46008118A3 /* DetectBottomScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E111DE212790BB46008118A3 /* DetectBottomScrollView.swift */; }; E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; }; E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; }; @@ -327,9 +328,6 @@ E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */; }; E126F741278A656C00A522BF /* ServerStreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E126F740278A656C00A522BF /* ServerStreamType.swift */; }; E126F742278A656C00A522BF /* ServerStreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E126F740278A656C00A522BF /* ServerStreamType.swift */; }; - E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; - E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; - E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E1347DB2279E3C6200BC6161 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1347DB1279E3C6200BC6161 /* Puppy */; }; E1347DB4279E3C9E00BC6161 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1347DB3279E3C9E00BC6161 /* Puppy */; }; E1347DB6279E3CA500BC6161 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1347DB5279E3CA500BC6161 /* Puppy */; }; @@ -782,7 +780,6 @@ E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStreamExtension.swift; sourceTree = ""; }; E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = ""; }; E126F740278A656C00A522BF /* ServerStreamType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerStreamType.swift; sourceTree = ""; }; - E131691626C583BC0074BFEE /* LogConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogConstructor.swift; sourceTree = ""; }; E1384943278036C70024FB48 /* VLCPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayerViewController.swift; sourceTree = ""; }; E13AD72D2798BC8D00FDCEE8 /* NativePlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativePlayerViewController.swift; sourceTree = ""; }; E13AD72F2798C60F00FDCEE8 /* NativePlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativePlayerViewController.swift; sourceTree = ""; }; @@ -894,7 +891,6 @@ buildActionMask = 2147483647; files = ( 62666E1727E501CC00EC0ECD /* CFNetwork.framework in Frameworks */, - 53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */, E11D83AF278FA998006E9776 /* NukeUI in Frameworks */, 62666DFA27E5013700EC0ECD /* TVVLCKit.xcframework in Frameworks */, 62666E3227E5021E00EC0ECD /* UIKit.framework in Frameworks */, @@ -933,6 +929,7 @@ 62666E3E27E503FA00EC0ECD /* MediaAccessibility.framework in Frameworks */, 62666DFF27E5016400EC0ECD /* CFNetwork.framework in Frameworks */, E13DD3D327168E65009D4DAF /* Defaults in Frameworks */, + E1101177281B1E8A006A3584 /* Puppy in Frameworks */, E1361DA7278FA7A300BEC523 /* NukeUI in Frameworks */, E1002B682793CFBA00E47059 /* Algorithms in Frameworks */, 62666E1127E501B900EC0ECD /* UIKit.framework in Frameworks */, @@ -1860,7 +1857,6 @@ isa = PBXGroup; children = ( E1FCD09526C47118007C8DCF /* ErrorMessage.swift */, - E131691626C583BC0074BFEE /* LogConstructor.swift */, E1FCD08726C35A0D007C8DCF /* NetworkError.swift */, ); path = Errors; @@ -1936,6 +1932,7 @@ E1361DA6278FA7A300BEC523 /* NukeUI */, E1002B672793CFBA00E47059 /* Algorithms */, 62666E3827E502CE00EC0ECD /* SwizzleSwift */, + E1101176281B1E8A006A3584 /* Puppy */, ); productName = JellyfinPlayer; productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */; @@ -2029,6 +2026,7 @@ E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */, E1002B662793CFBA00E47059 /* XCRemoteSwiftPackageReference "swift-algorithms" */, 62666E3727E502CE00EC0ECD /* XCRemoteSwiftPackageReference "SwizzleSwift" */, + E1101175281B1E8A006A3584 /* XCRemoteSwiftPackageReference "Puppy" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; projectDirPath = ""; @@ -2339,7 +2337,6 @@ 5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */, 531690FA267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift in Sources */, C4BE0764271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */, - E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */, E1E5D5512783E67700692DFE /* ExperimentalSettingsView.swift in Sources */, E1A2C160279A7DCA005EC829 /* AboutView.swift in Sources */, C4BE076A271FC164003F4AD1 /* TVLibrariesView.swift in Sources */, @@ -2416,7 +2413,6 @@ C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */, 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */, 62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */, - E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */, 5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */, E14F7D0926DB36F7007C3AE6 /* ItemLandscapeMainView.swift in Sources */, E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */, @@ -2538,7 +2534,6 @@ E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */, 6220D0AF26D5EABE00B8E046 /* ViewExtensions.swift in Sources */, E13DD3D7271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */, - E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */, E13DD3CA27164B80009D4DAF /* SwiftfinStore.swift in Sources */, E10EAA51277BBCC4000269ED /* CGSizeExtensions.swift in Sources */, 62E1DCC5273CE19800C9AE76 /* URLExtensions.swift in Sources */, @@ -2884,7 +2879,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 70; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2920,7 +2915,7 @@ CURRENT_PROJECT_VERSION = 70; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2950,7 +2945,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 70; - DEVELOPMENT_TEAM = 4BHXT8RHFR; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2976,7 +2971,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 70; - DEVELOPMENT_TEAM = 4BHXT8RHFR; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -3108,6 +3103,14 @@ kind = branch; }; }; + E1101175281B1E8A006A3584 /* XCRemoteSwiftPackageReference "Puppy" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sushichop/Puppy"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.5.0; + }; + }; E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/CombineCommunity/CombineExt"; @@ -3237,6 +3240,11 @@ package = E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */; productName = Sliders; }; + E1101176281B1E8A006A3584 /* Puppy */ = { + isa = XCSwiftPackageProductDependency; + package = E1101175281B1E8A006A3584 /* XCRemoteSwiftPackageReference "Puppy" */; + productName = Puppy; + }; E11D83AE278FA998006E9776 /* NukeUI */ = { isa = XCSwiftPackageProductDependency; package = E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */; diff --git a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c3e87965..b8168ffe 100644 --- a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Flight-School/AnyCodable", "state" : { - "revision" : "22f302d4c048aafcda09a4ab5b8c0b03855316fb", - "version" : "0.6.3" + "revision" : "11423ef0c756e8a1f6b4bb576dab9d97bc016c70", + "version" : "0.6.4" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kaishin/Gifu", "state" : { - "revision" : "0ffe24744cc3d82ab9edece53670d0352c6d5507", - "version" : "3.3.0" + "revision" : "51f2eab32903e336f590c013267cfa4d7f8b06c4", + "version" : "3.3.1" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kean/Nuke.git", "state" : { - "revision" : "78fa963b8491fc520791d8c2a509f1b8593d8aae", - "version" : "10.7.1" + "revision" : "0ea7545b5c918285aacc044dc75048625c8257cc", + "version" : "10.8.0" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kean/NukeUI", "state" : { - "revision" : "71398392943f2538fd0f2ebc6f282920f6775b0c", - "version" : "0.8.0" + "revision" : "17f26c07e6b1d3b9258287f99f528111fcd7b7ad", + "version" : "0.8.1" } }, { @@ -140,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/siteline/SwiftUI-Introspect", "state" : { - "revision" : "2e09be8af614401bc9f87d40093ec19ce56ccaf2", - "version" : "0.1.3" + "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24", + "version" : "0.1.4" } }, { @@ -150,7 +150,7 @@ "location" : "https://github.com/spacenation/swiftui-sliders", "state" : { "branch" : "master", - "revision" : "518bed3bfc7bd522f3c49404a0d1efb98fa1bf2c" + "revision" : "538e16b35ad7a066a8f5624da9ecee6327886bf7" } }, { diff --git a/Swiftfin/App/AppDelegate.swift b/Swiftfin/App/AppDelegate.swift index 1d72db97..5ed7e036 100644 --- a/Swiftfin/App/AppDelegate.swift +++ b/Swiftfin/App/AppDelegate.swift @@ -19,7 +19,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { // Lazily initialize datastack _ = SwiftfinStore.dataStack - LogManager.setup() + LogManager.setup() let audioSession = AVAudioSession.sharedInstance() do { diff --git a/Swiftfin/Components/PortraitHStackView.swift b/Swiftfin/Components/PortraitHStackView.swift index a9aa57c0..79261be4 100644 --- a/Swiftfin/Components/PortraitHStackView.swift +++ b/Swiftfin/Components/PortraitHStackView.swift @@ -48,9 +48,9 @@ struct PortraitImageHStackView: View { failureView: { InitialFailureView(item.failureInitials) }) - .portraitPoster(width: maxWidth) - .shadow(radius: 4, y: 2) - .accessibilityIgnoresInvertColors() + .portraitPoster(width: maxWidth) + .shadow(radius: 4, y: 2) + .accessibilityIgnoresInvertColors() if item.showTitle { Text(item.title) diff --git a/Swiftfin/Info.plist b/Swiftfin/Info.plist index 8948aa04..e78d7487 100644 --- a/Swiftfin/Info.plist +++ b/Swiftfin/Info.plist @@ -2,10 +2,6 @@ - LSSupportsOpeningDocumentsInPlace - - UIFileSharingEnabled - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -39,6 +35,8 @@ LSRequiresIPhoneOS + LSSupportsOpeningDocumentsInPlace + NSAppTransportSecurity NSAllowsArbitraryLoads @@ -65,6 +63,8 @@ network. UIApplicationSupportsIndirectInputEvents + UIFileSharingEnabled + UILaunchScreen UIColorName diff --git a/Swiftfin/Views/ConnectToServerView.swift b/Swiftfin/Views/ConnectToServerView.swift index 41efc43b..0c09d6d1 100644 --- a/Swiftfin/Views/ConnectToServerView.swift +++ b/Swiftfin/Views/ConnectToServerView.swift @@ -103,7 +103,7 @@ struct ConnectToServerView: View { } .alert(item: $viewModel.errorMessage) { _ in Alert(title: Text(viewModel.alertTitle), - message: Text(viewModel.errorMessage?.displayMessage ?? L10n.unknownError), + message: Text(viewModel.errorMessage?.message ?? L10n.unknownError), dismissButton: .cancel()) } .alert(item: $viewModel.addServerURIPayload) { _ in diff --git a/Swiftfin/Views/ContinueWatchingView.swift b/Swiftfin/Views/ContinueWatchingView.swift index e4f55910..505e187f 100644 --- a/Swiftfin/Views/ContinueWatchingView.swift +++ b/Swiftfin/Views/ContinueWatchingView.swift @@ -33,13 +33,13 @@ struct ContinueWatchingView: View { item.getSeriesThumbImage(maxWidth: 320), item.getSeriesBackdropImage(maxWidth: 320), ]) - .frame(width: 320, height: 180) + .frame(width: 320, height: 180) } else { ImageView(sources: [ item.getThumbImage(maxWidth: 320), item.getBackdropImage(maxWidth: 320), ]) - .frame(width: 320, height: 180) + .frame(width: 320, height: 180) } } .accessibilityIgnoresInvertColors() diff --git a/Swiftfin/Views/HomeView.swift b/Swiftfin/Views/HomeView.swift index 0d2fafeb..a7d1fbe1 100644 --- a/Swiftfin/Views/HomeView.swift +++ b/Swiftfin/Views/HomeView.swift @@ -35,7 +35,7 @@ struct HomeView: View { } Text("\(errorMessage.code)") - Text(errorMessage.displayMessage) + Text(errorMessage.message) .frame(minWidth: 50, maxWidth: 240) .multilineTextAlignment(.center) diff --git a/Swiftfin/Views/ItemView/ItemViewBody.swift b/Swiftfin/Views/ItemView/ItemViewBody.swift index 55dfc8a5..5e9b959d 100644 --- a/Swiftfin/Views/ItemView/ItemViewBody.swift +++ b/Swiftfin/Views/ItemView/ItemViewBody.swift @@ -69,7 +69,7 @@ struct ItemViewBody: View { selectedAction: { genre in itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title)) }) - .padding(.bottom) + .padding(.bottom) } // MARK: Studios diff --git a/Swiftfin/Views/UserSignInView.swift b/Swiftfin/Views/UserSignInView.swift index b8002034..eec3f212 100644 --- a/Swiftfin/Views/UserSignInView.swift +++ b/Swiftfin/Views/UserSignInView.swift @@ -50,7 +50,7 @@ struct UserSignInView: View { } .alert(item: $viewModel.errorMessage) { _ in Alert(title: Text(viewModel.alertTitle), - message: Text(viewModel.errorMessage?.displayMessage ?? L10n.unknownError), + message: Text(viewModel.errorMessage?.message ?? L10n.unknownError), dismissButton: .cancel()) } .navigationTitle(L10n.signIn) diff --git a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift index 9bb57530..5530e798 100644 --- a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift +++ b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift @@ -375,16 +375,16 @@ struct VLCPlayerOverlayView: View { ValueSlider(value: $viewModel.sliderPercentage, onEditingChanged: { editing in viewModel.sliderIsScrubbing = editing }) - .valueSliderStyle(HorizontalValueSliderStyle(track: - HorizontalValueTrack(view: - Capsule().foregroundColor(.purple)) - .background(Capsule().foregroundColor(Color.gray.opacity(0.25))) - .frame(height: 4), - thumb: Circle().foregroundColor(.purple), - thumbSize: CGSize.Circle(radius: viewModel.sliderIsScrubbing ? 20 : 15), - thumbInteractiveSize: CGSize.Circle(radius: 40), - options: .defaultOptions)) - .frame(maxHeight: 50) + .valueSliderStyle(HorizontalValueSliderStyle(track: + HorizontalValueTrack(view: + Capsule().foregroundColor(.purple)) + .background(Capsule().foregroundColor(Color.gray.opacity(0.25))) + .frame(height: 4), + thumb: Circle().foregroundColor(.purple), + thumbSize: CGSize.Circle(radius: viewModel.sliderIsScrubbing ? 20 : 15), + thumbInteractiveSize: CGSize.Circle(radius: 40), + options: .defaultOptions)) + .frame(maxHeight: 50) Text(viewModel.rightLabelText) .font(.system(size: 18, weight: .semibold, design: .default)) diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index 6554c737257d7b2fd798593430cb209e84c69ad6..36e2d4a016b7416c914a1350434ece144f74968f 100644 GIT binary patch delta 70 zcmaEncpz!R3j;+ZhEj$+hD3%Wh8%`eh7g8)1~-OGphyaX2asLNP%` Date: Thu, 28 Apr 2022 15:04:34 -0600 Subject: [PATCH 17/25] fix tv files --- .../Coordinators/MainCoordinator/tvOSMainCoordinator.swift | 4 ++-- Swiftfin tvOS/Views/ConnectToServerView.swift | 2 +- Swiftfin tvOS/Views/UserSignInView.swift | 2 +- .../Views/VideoPlayer/LiveTVPlayerViewController.swift | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift index 8e1d6390..3f7e48a9 100644 --- a/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift @@ -46,13 +46,13 @@ final class MainCoordinator: NavigationCoordinatable { @objc func didSignIn() { - LogManager.shared.log.info("Received `didSignIn` from NSNotificationCenter.") + LogManager.log.info("Received `didSignIn` from NSNotificationCenter.") root(\.mainTab) } @objc func didSignOut() { - LogManager.shared.log.info("Received `didSignOut` from NSNotificationCenter.") + LogManager.log.info("Received `didSignOut` from NSNotificationCenter.") root(\.serverList) } diff --git a/Swiftfin tvOS/Views/ConnectToServerView.swift b/Swiftfin tvOS/Views/ConnectToServerView.swift index 26da99c7..690777bd 100644 --- a/Swiftfin tvOS/Views/ConnectToServerView.swift +++ b/Swiftfin tvOS/Views/ConnectToServerView.swift @@ -77,7 +77,7 @@ struct ConnectToServerView: View { } .alert(item: $viewModel.errorMessage) { _ in Alert(title: Text(viewModel.alertTitle), - message: Text(viewModel.errorMessage?.displayMessage ?? L10n.unknownError), + message: Text(viewModel.errorMessage?.message ?? L10n.unknownError), dismissButton: .cancel()) } .navigationTitle(L10n.connect) diff --git a/Swiftfin tvOS/Views/UserSignInView.swift b/Swiftfin tvOS/Views/UserSignInView.swift index 739599a5..b779f7bb 100644 --- a/Swiftfin tvOS/Views/UserSignInView.swift +++ b/Swiftfin tvOS/Views/UserSignInView.swift @@ -49,7 +49,7 @@ struct UserSignInView: View { } .alert(item: $viewModel.errorMessage) { _ in Alert(title: Text(viewModel.alertTitle), - message: Text(viewModel.errorMessage?.displayMessage ?? L10n.unknownError), + message: Text(viewModel.errorMessage?.message ?? L10n.unknownError), dismissButton: .cancel()) } .navigationTitle(L10n.signIn) diff --git a/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift b/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift index e726e723..0878a873 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift @@ -464,11 +464,11 @@ extension LiveTVPlayerViewController { viewModel = newViewModel if viewModel.streamType == .direct { - LogManager.shared.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? "--")") + LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? "--")") } else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] { - LogManager.shared.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? "--")") + LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? "--")") } else { - LogManager.shared.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? "--")") + LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? "--")") } } From a22ef63074ae0c314a80eb63e4a741ee73b686d9 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 28 Apr 2022 15:12:17 -0600 Subject: [PATCH 18/25] Update ErrorMessage.swift --- Shared/Errors/ErrorMessage.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Shared/Errors/ErrorMessage.swift b/Shared/Errors/ErrorMessage.swift index e316d0aa..75b72f5b 100644 --- a/Shared/Errors/ErrorMessage.swift +++ b/Shared/Errors/ErrorMessage.swift @@ -23,7 +23,6 @@ struct ErrorMessage: Identifiable { "\(code)\(title)\(message)" } - /// If the custom displayMessage is `nil`, it will be set to the given logConstructor's message init(code: Int, title: String, message: String) { self.code = code self.title = title From 842971da1372623253658c75e21f84aed5c676b6 Mon Sep 17 00:00:00 2001 From: Joe Diragi Date: Sat, 30 Apr 2022 16:28:16 -0400 Subject: [PATCH 19/25] Fixes some compiler warnings --- Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift | 2 +- Swiftfin.xcodeproj/project.pbxproj | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift b/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift index 78af9836..ffe7a4b1 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift @@ -324,7 +324,7 @@ struct SmallMediaStreamSelectionView: View { ScrollView(.horizontal, showsIndicators: false) { ScrollViewReader { reader in HStack { - ForEach(0 ..< viewModel.chapters.count) { chapterIndex in + ForEach(0 ..< viewModel.chapters.count, id: \.self) { chapterIndex in VStack(alignment: .leading) { Button { viewModel.playerOverlayDelegate?.didSelectChapter(viewModel.chapters[chapterIndex]) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 537b1932..09fdcef5 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -191,7 +191,6 @@ 62666E2127E501E400EC0ECD /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2027E501E400EC0ECD /* CoreVideo.framework */; }; 62666E2327E501EB00EC0ECD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2227E501EB00EC0ECD /* Foundation.framework */; }; 62666E2427E501F300EC0ECD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5362E4BC267D40D8000E2F71 /* Foundation.framework */; }; - 62666E2A27E5020A00EC0ECD /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2927E5020A00EC0ECD /* OpenGLES.framework */; }; 62666E2C27E5021000EC0ECD /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2B27E5021000EC0ECD /* QuartzCore.framework */; }; 62666E2E27E5021400EC0ECD /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2D27E5021400EC0ECD /* Security.framework */; }; 62666E3027E5021800EC0ECD /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2F27E5021800EC0ECD /* VideoToolbox.framework */; }; @@ -895,7 +894,6 @@ 62666DFA27E5013700EC0ECD /* TVVLCKit.xcframework in Frameworks */, 62666E3227E5021E00EC0ECD /* UIKit.framework in Frameworks */, E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */, - 62666E2A27E5020A00EC0ECD /* OpenGLES.framework in Frameworks */, E1002B6B2793E36600E47059 /* Algorithms in Frameworks */, 62666E1D27E501DB00EC0ECD /* CoreMedia.framework in Frameworks */, 62666E3027E5021800EC0ECD /* VideoToolbox.framework in Frameworks */, From a6bcd668d5ad931acc079a0593b1f51437efc75d Mon Sep 17 00:00:00 2001 From: Joe Diragi Date: Sat, 30 Apr 2022 16:50:14 -0400 Subject: [PATCH 20/25] Fixes dangling pointer warning --- Shared/ServerDiscovery/UDPBroadCastConnection.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Shared/ServerDiscovery/UDPBroadCastConnection.swift b/Shared/ServerDiscovery/UDPBroadCastConnection.swift index 8261fc41..d917ca72 100644 --- a/Shared/ServerDiscovery/UDPBroadCastConnection.swift +++ b/Shared/ServerDiscovery/UDPBroadCastConnection.swift @@ -131,9 +131,11 @@ open class UDPBroadcastConnection { var socketAddressLength = socklen_t(MemoryLayout.size) let response = [UInt8](repeating: 0, count: 4096) let UDPSocket = Int32(source.handle) + let pointer = UnsafeMutablePointer<[UInt8]>.allocate(capacity: response.capacity) + pointer.initialize(to: response) let bytesRead = withUnsafeMutablePointer(to: &socketAddress) { - recvfrom(UDPSocket, UnsafeMutableRawPointer(mutating: response), response.count, 0, + recvfrom(UDPSocket, pointer, response.count, 0, UnsafeMutableRawPointer($0).bindMemory(to: sockaddr.self, capacity: 1), &socketAddressLength) } From 0f923439707ddbd7abe961a187ba85a14d849a34 Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Sun, 1 May 2022 05:12:23 +0900 Subject: [PATCH 21/25] VideoPlayer's Double tap related UX improvement Change VideoPlayer's overlay show implementation Add player gestures lock gesture settings --- Shared/Generated/Strings.swift | 2 + .../SwiftfinStore/SwiftfinStoreDefaults.swift | 2 + .../VideoPlayerViewModel.swift | 5 + .../Views/SettingsView/SettingsView.swift | 4 + .../Overlays/VLCPlayerOverlayView.swift | 23 ++-- .../VideoPlayer/PlayerOverlayDelegate.swift | 4 +- .../VideoPlayer/VLCPlayerViewController.swift | 101 +++++++++++++----- Translations/en.lproj/Localizable.strings | Bin 12520 -> 12674 bytes 8 files changed, 107 insertions(+), 34 deletions(-) diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index 3f18c5d0..aab098bd 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -240,6 +240,8 @@ internal enum L10n { internal static var playbackSettings: String { return L10n.tr("Localizable", "playbackSettings") } /// Playback Speed internal static var playbackSpeed: String { return L10n.tr("Localizable", "playbackSpeed") } + /// Player Gestures Lock Gesture Enabled + internal static var playerGesturesLockGestureEnabled: String { return L10n.tr("Localizable", "playerGesturesLockGestureEnabled") } /// Play From Beginning internal static var playFromBeginning: String { return L10n.tr("Localizable", "playFromBeginning") } /// Play Next diff --git a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift index d2300763..763dbaac 100644 --- a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift +++ b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift @@ -48,6 +48,8 @@ extension Defaults.Keys { static let jumpGesturesEnabled = Key("gesturesEnabled", default: true, suite: SwiftfinStore.Defaults.generalSuite) static let systemControlGesturesEnabled = Key("systemControlGesturesEnabled", default: true, suite: SwiftfinStore.Defaults.generalSuite) + static let playerGesturesLockGestureEnabled = Key("playerGesturesLockGestureEnabled", default: true, + suite: SwiftfinStore.Defaults.generalSuite) static let videoPlayerJumpForward = Key("videoPlayerJumpForward", default: .fifteen, suite: SwiftfinStore.Defaults.generalSuite) static let videoPlayerJumpBackward = Key("videoPlayerJumpBackward", default: .fifteen, diff --git a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift index 10dc2140..db64d182 100644 --- a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift @@ -95,6 +95,9 @@ final class VideoPlayerViewModel: ViewModel { @Published var mediaItems: [BaseItemDto.ItemDetail] + @Published + var isHiddenOverlay = false + // MARK: ShouldShowItems let shouldShowPlayPreviousItem: Bool @@ -116,6 +119,7 @@ final class VideoPlayerViewModel: ViewModel { let overlayType: OverlayType let jumpGesturesEnabled: Bool let systemControlGesturesEnabled: Bool + let playerGesturesLockGestureEnabled: Bool let resumeOffset: Bool let streamType: ServerStreamType let container: String @@ -244,6 +248,7 @@ final class VideoPlayerViewModel: ViewModel { self.jumpForwardLength = Defaults[.videoPlayerJumpForward] self.jumpGesturesEnabled = Defaults[.jumpGesturesEnabled] self.systemControlGesturesEnabled = Defaults[.systemControlGesturesEnabled] + self.playerGesturesLockGestureEnabled = Defaults[.playerGesturesLockGestureEnabled] self.shouldShowJumpButtonsInOverlayMenu = Defaults[.shouldShowJumpButtonsInOverlayMenu] self.resumeOffset = Defaults[.resumeOffset] diff --git a/Swiftfin/Views/SettingsView/SettingsView.swift b/Swiftfin/Views/SettingsView/SettingsView.swift index e1a0ffae..0ffb94f2 100644 --- a/Swiftfin/Views/SettingsView/SettingsView.swift +++ b/Swiftfin/Views/SettingsView/SettingsView.swift @@ -40,6 +40,8 @@ struct SettingsView: View { var jumpGesturesEnabled @Default(.systemControlGesturesEnabled) var systemControlGesturesEnabled + @Default(.playerGesturesLockGestureEnabled) + var playerGesturesLockGestureEnabled @Default(.resumeOffset) var resumeOffset @Default(.subtitleSize) @@ -111,6 +113,8 @@ struct SettingsView: View { Toggle(L10n.systemControlGesturesEnabled, isOn: $systemControlGesturesEnabled) + Toggle(L10n.playerGesturesLockGestureEnabled, isOn: $playerGesturesLockGestureEnabled) + Toggle(L10n.resume5SecondOffset, isOn: $resumeOffset) Button { diff --git a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift index 5530e798..3ec74867 100644 --- a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift +++ b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift @@ -400,13 +400,11 @@ struct VLCPlayerOverlayView: View { .foregroundColor(Color.white) } - var body: some View { + @ViewBuilder + var contents: some View { if viewModel.overlayType == .normal { mainBody .contentShape(Rectangle()) - .onTapGesture { - viewModel.playerOverlayDelegate?.didGenerallyTap() - } .background { Color(uiColor: .black.withAlphaComponent(0.5)) .ignoresSafeArea() @@ -414,11 +412,22 @@ struct VLCPlayerOverlayView: View { } else { mainBody .contentShape(Rectangle()) - .onTapGesture { - viewModel.playerOverlayDelegate?.didGenerallyTap() - } } } + + var body: some View { + contents + .onLongPressGesture { + guard viewModel.playerGesturesLockGestureEnabled else { return } + viewModel.playerOverlayDelegate?.didGenerallyTap(point: nil) + viewModel.playerOverlayDelegate?.didLongPress() + } + .gesture(DragGesture(minimumDistance: 0) + .onEnded { value in + viewModel.playerOverlayDelegate?.didGenerallyTap(point: value.location) + }) + .opacity(viewModel.isHiddenOverlay ? 0 : 1) + } } struct VLCPlayerCompactOverlayView_Previews: PreviewProvider { diff --git a/Swiftfin/Views/VideoPlayer/PlayerOverlayDelegate.swift b/Swiftfin/Views/VideoPlayer/PlayerOverlayDelegate.swift index 4977b3a4..466ca609 100644 --- a/Swiftfin/Views/VideoPlayer/PlayerOverlayDelegate.swift +++ b/Swiftfin/Views/VideoPlayer/PlayerOverlayDelegate.swift @@ -8,6 +8,7 @@ import Foundation import JellyfinAPI +import UIKit protocol PlayerOverlayDelegate { @@ -19,7 +20,8 @@ protocol PlayerOverlayDelegate { func didSelectForward() func didSelectMain() - func didGenerallyTap() + func didGenerallyTap(point: CGPoint?) + func didLongPress() func didBeginScrubbing() func didEndScrubbing() diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index 5f8a3f96..857bde5c 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -46,6 +46,10 @@ class VLCPlayerViewController: UIViewController { private var panBeganBrightness = CGFloat.zero private var panBeganVolumeValue = Float.zero private var panBeganPoint = CGPoint.zero + private var tapLocationStack = [CGPoint]() + private var isJumping = false + private var jumpingCompletionWork: DispatchWorkItem? + private var isTapWhenJumping = false private lazy var videoContentView = makeVideoContentView() private lazy var mainGestureView = makeMainGestureView() @@ -230,24 +234,17 @@ class VLCPlayerViewController: UIViewController { let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap)) - let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(didDoubleTap)) - doubleTapGesture.numberOfTapsRequired = 2 - let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(didPinch(_:))) let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(_:))) - let longPeessGesture = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress)) + let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress)) view.addGestureRecognizer(singleTapGesture) view.addGestureRecognizer(pinchGesture) - view.addGestureRecognizer(longPeessGesture) - if viewModel.jumpGesturesEnabled { - view.addGestureRecognizer(doubleTapGesture) - singleTapGesture.require(toFail: doubleTapGesture) - singleTapGesture.delaysTouchesBegan = true - doubleTapGesture.delaysTouchesBegan = true + if viewModel.playerGesturesLockGestureEnabled { + view.addGestureRecognizer(longPressGesture) } if viewModel.systemControlGesturesEnabled { @@ -264,6 +261,7 @@ class VLCPlayerViewController: UIViewController { label.alpha = 0 label.translatesAutoresizingMaskIntoConstraints = false label.font = .systemFont(ofSize: 48) + label.layer.zPosition = 1 return label } @@ -271,11 +269,13 @@ class VLCPlayerViewController: UIViewController { private func makeGestureLockedOverlayView() -> UIView { let backgroundView = UIView() + backgroundView.layer.zPosition = 1 backgroundView.alpha = 0 backgroundView.translatesAutoresizingMaskIntoConstraints = false let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in self?.isGesturesLocked = false self?.hideLockedOverlay() + self?.didGenerallyTap() })) button.translatesAutoresizingMaskIntoConstraints = false button.setImage(UIImage(systemName: "lock.open", withConfiguration: UIImage.SymbolConfiguration(pointSize: 48))? @@ -295,21 +295,12 @@ class VLCPlayerViewController: UIViewController { } @objc - private func didTap() { - didGenerallyTap() + private func didTap(_ gestureRecognizer: UITapGestureRecognizer) { + didGenerallyTap(point: gestureRecognizer.location(in: mainGestureView)) } @objc - private func didDoubleTap(_ gestureRecognizer: UITapGestureRecognizer) { - if gestureRecognizer.location(in: mainGestureView).x > (mainGestureView.frame.width / 2) { - didSelectForward() - } else { - didSelectBackward() - } - } - - @objc - private func didLongPress() { + func didLongPress() { guard !isGesturesLocked else { return } isGesturesLocked = true didGenerallyTap() @@ -647,9 +638,10 @@ extension VLCPlayerViewController { guard let overlayHostingController = currentOverlayHostingController else { return } guard overlayHostingController.view.alpha != 1 else { return } + overlayHostingController.view.alpha = 1 - UIView.animate(withDuration: 0.2) { - overlayHostingController.view.alpha = 1 + withAnimation(.easeInOut(duration: 0.2)) { [weak self] in + self?.viewModel.isHiddenOverlay = false } } @@ -660,8 +652,16 @@ extension VLCPlayerViewController { guard overlayHostingController.view.alpha != 0 else { return } - UIView.animate(withDuration: 0.2) { + // for gestures UX + view.exchangeSubview(at: view.subviews.firstIndex(of: mainGestureView)!, + withSubviewAt: view.subviews.firstIndex(of: overlayHostingController.view)!) + UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) { overlayHostingController.view.alpha = 0 + } completion: { [weak self] _ in + guard let self = self else { return } + self.view.exchangeSubview(at: self.view.subviews.firstIndex(of: self.mainGestureView)!, + withSubviewAt: self.view.subviews.firstIndex(of: overlayHostingController.view)!) + self.viewModel.isHiddenOverlay = true } } @@ -998,16 +998,65 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { } } - func didGenerallyTap() { + func didGenerallyTap(point: CGPoint? = nil) { if isGesturesLocked { toggleLockedOverlay() } else { + if viewModel.jumpGesturesEnabled, + let point = point + { + let tempStack = tapLocationStack + tapLocationStack.append(point) + + if isSameLocationWithLast(point: point, in: tempStack) { + isTapWhenJumping = false + isJumping = true + tapLocationStack.removeAll() + jumpingCompletionWork?.cancel() + jumpingCompletionWork = DispatchWorkItem(block: { [weak self] in + guard let self = self else { return } + self.isJumping = false + guard self.isTapWhenJumping else { return } + self.isTapWhenJumping = false + self.toggleOverlay() + }) + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: jumpingCompletionWork!) + + hideOverlay() + if point.x > (mainGestureView.frame.width / 2) { + didSelectForward() + } else { + didSelectBackward() + } + return + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in + guard let self = self else { return } + guard !self.tapLocationStack.isEmpty else { return } + self.tapLocationStack.removeFirst() + } + } + } + guard !isJumping else { + isTapWhenJumping = true + return + } + toggleOverlay() } restartOverlayDismissTimer(interval: 5) } + private func isSameLocationWithLast(point: CGPoint, in stack: [CGPoint]) -> Bool { + guard let last = stack.last else { return false } + if last.x > (mainGestureView.frame.width / 2) { + return point.x > (mainGestureView.frame.width / 2) + } else { + return point.x <= (mainGestureView.frame.width / 2) + } + } + func didBeginScrubbing() { stopOverlayDismissTimer() } diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index 6554c737257d7b2fd798593430cb209e84c69ad6..268945c509b96192aafd5b8db83b2c963f22c82c 100644 GIT binary patch delta 106 zcmaEn*p$4%NWrjxA%`K6p^_n$p@_j9NES1cFq8t>AeIk9K0`7?HmdmKczMamZ)7-_ ZfjTDF%ZrM`R4JgTn3==CkF+V From 9b85a01958f199c0902e78deb382d617edf72c35 Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Sun, 1 May 2022 06:37:03 +0900 Subject: [PATCH 22/25] fix gestures lock icon --- Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index 857bde5c..7a8e14e2 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -278,7 +278,7 @@ class VLCPlayerViewController: UIViewController { self?.didGenerallyTap() })) button.translatesAutoresizingMaskIntoConstraints = false - button.setImage(UIImage(systemName: "lock.open", withConfiguration: UIImage.SymbolConfiguration(pointSize: 48))? + button.setImage(UIImage(systemName: "lock.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 48))? .withTintColor(.white), for: .normal) backgroundView.addSubview(button) From b43abf1548c55e3b217c5283b1fa7a10d4e870ba Mon Sep 17 00:00:00 2001 From: Joe Diragi Date: Sat, 30 Apr 2022 19:34:11 -0400 Subject: [PATCH 23/25] Runs SwiftLint and adds back OpenGLES --- .../UDPBroadCastConnection.swift | 485 +++++++------ .../Overlays/SmallMenuOverlay.swift | 642 +++++++++--------- Swiftfin.xcodeproj/project.pbxproj | 2 + 3 files changed, 562 insertions(+), 567 deletions(-) diff --git a/Shared/ServerDiscovery/UDPBroadCastConnection.swift b/Shared/ServerDiscovery/UDPBroadCastConnection.swift index d917ca72..abb8677a 100644 --- a/Shared/ServerDiscovery/UDPBroadCastConnection.swift +++ b/Shared/ServerDiscovery/UDPBroadCastConnection.swift @@ -16,301 +16,298 @@ let INADDR_BROADCAST = in_addr(s_addr: 0xFFFF_FFFF) /// An object representing the UDP broadcast connection. Uses a dispatch source to handle the incoming traffic on the UDP socket. open class UDPBroadcastConnection { + // MARK: Properties - // MARK: Properties + /// The address of the UDP socket. + var address: sockaddr_in - /// The address of the UDP socket. - var address: sockaddr_in + /// Type of a closure that handles incoming UDP packets. + public typealias ReceiveHandler = (_ ipAddress: String, _ port: Int, _ response: Data) -> Void + /// Closure that handles incoming UDP packets. + var handler: ReceiveHandler? - /// Type of a closure that handles incoming UDP packets. - public typealias ReceiveHandler = (_ ipAddress: String, _ port: Int, _ response: Data) -> Void - /// Closure that handles incoming UDP packets. - var handler: ReceiveHandler? + /// Type of a closure that handles errors that were encountered during receiving UDP packets. + public typealias ErrorHandler = (_ error: ConnectionError) -> Void + /// Closure that handles errors that were encountered during receiving UDP packets. + var errorHandler: ErrorHandler? - /// Type of a closure that handles errors that were encountered during receiving UDP packets. - public typealias ErrorHandler = (_ error: ConnectionError) -> Void - /// Closure that handles errors that were encountered during receiving UDP packets. - var errorHandler: ErrorHandler? + /// A dispatch source for reading data from the UDP socket. + var responseSource: DispatchSourceRead? - /// A dispatch source for reading data from the UDP socket. - var responseSource: DispatchSourceRead? + /// The dispatch queue to run responseSource & reconnection on + var dispatchQueue = DispatchQueue.main - /// The dispatch queue to run responseSource & reconnection on - var dispatchQueue = DispatchQueue.main + /// Bind to port to start listening without first sending a message + var shouldBeBound: Bool = false - /// Bind to port to start listening without first sending a message - var shouldBeBound: Bool = false + // MARK: Initializers - // MARK: Initializers + /// Initializes the UDP connection with the correct port address. - /// Initializes the UDP connection with the correct port address. + /// - Note: This doesn't open a socket! The socket is opened transparently as needed when sending broadcast messages. If you want to open a socket immediately, use the `bindIt` parameter. This will also try to reopen the socket if it gets closed. + /// + /// - Parameters: + /// - port: Number of the UDP port to use. + /// - bindIt: Opens a port immediately if true, on demand if false. Default is false. + /// - handler: Handler that gets called when data is received. + /// - errorHandler: Handler that gets called when an error occurs. + /// - Throws: Throws a `ConnectionError` if an error occurs. + public init(port: UInt16, bindIt: Bool = false, handler: ReceiveHandler?, errorHandler: ErrorHandler?) throws { + self.address = sockaddr_in(sin_len: __uint8_t(MemoryLayout.size), + sin_family: sa_family_t(AF_INET), + sin_port: UDPBroadcastConnection.htonsPort(port: port), + sin_addr: INADDR_BROADCAST, + sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) - /// - Note: This doesn't open a socket! The socket is opened transparently as needed when sending broadcast messages. If you want to open a socket immediately, use the `bindIt` parameter. This will also try to reopen the socket if it gets closed. - /// - /// - Parameters: - /// - port: Number of the UDP port to use. - /// - bindIt: Opens a port immediately if true, on demand if false. Default is false. - /// - handler: Handler that gets called when data is received. - /// - errorHandler: Handler that gets called when an error occurs. - /// - Throws: Throws a `ConnectionError` if an error occurs. - public init(port: UInt16, bindIt: Bool = false, handler: ReceiveHandler?, errorHandler: ErrorHandler?) throws { - self.address = sockaddr_in(sin_len: __uint8_t(MemoryLayout.size), - sin_family: sa_family_t(AF_INET), - sin_port: UDPBroadcastConnection.htonsPort(port: port), - sin_addr: INADDR_BROADCAST, - sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) + self.handler = handler + self.errorHandler = errorHandler + self.shouldBeBound = bindIt + if bindIt { + try createSocket() + } + } - self.handler = handler - self.errorHandler = errorHandler - self.shouldBeBound = bindIt - if bindIt { - try createSocket() - } - } + deinit { + if responseSource != nil { + responseSource!.cancel() + } + } - deinit { - if responseSource != nil { - responseSource!.cancel() - } - } + // MARK: Interface - // MARK: Interface + /// Create a UDP socket for broadcasting and set up cancel and event handlers + /// + /// - Throws: Throws a `ConnectionError` if an error occurs. + fileprivate func createSocket() throws { + // Create new socket + let newSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) + guard newSocket > 0 else { throw ConnectionError.createSocketFailed } - /// Create a UDP socket for broadcasting and set up cancel and event handlers - /// - /// - Throws: Throws a `ConnectionError` if an error occurs. - fileprivate func createSocket() throws { + // Enable broadcast on socket + var broadcastEnable = Int32(1) + let ret = setsockopt(newSocket, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, socklen_t(MemoryLayout.size)) + if ret == -1 { + debugPrint("Couldn't enable broadcast on socket") + close(newSocket) + throw ConnectionError.enableBroadcastFailed + } - // Create new socket - let newSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) - guard newSocket > 0 else { throw ConnectionError.createSocketFailed } + // Bind socket if needed + if shouldBeBound { + var saddr = sockaddr(sa_len: 0, sa_family: 0, + sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + address.sin_addr = INADDR_ANY + memcpy(&saddr, &address, MemoryLayout.size) + address.sin_addr = INADDR_BROADCAST + let isBound = bind(newSocket, &saddr, socklen_t(MemoryLayout.size)) + if isBound == -1 { + debugPrint("Couldn't bind socket") + close(newSocket) + throw ConnectionError.bindSocketFailed + } + } - // Enable broadcast on socket - var broadcastEnable = Int32(1) - let ret = setsockopt(newSocket, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, socklen_t(MemoryLayout.size)) - if ret == -1 { - debugPrint("Couldn't enable broadcast on socket") - close(newSocket) - throw ConnectionError.enableBroadcastFailed - } + // Disable global SIGPIPE handler so that the app doesn't crash + setNoSigPipe(socket: newSocket) - // Bind socket if needed - if shouldBeBound { - var saddr = sockaddr(sa_len: 0, sa_family: 0, - sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - self.address.sin_addr = INADDR_ANY - memcpy(&saddr, &self.address, MemoryLayout.size) - self.address.sin_addr = INADDR_BROADCAST - let isBound = bind(newSocket, &saddr, socklen_t(MemoryLayout.size)) - if isBound == -1 { - debugPrint("Couldn't bind socket") - close(newSocket) - throw ConnectionError.bindSocketFailed - } - } + // Set up a dispatch source + let newResponseSource = DispatchSource.makeReadSource(fileDescriptor: newSocket, queue: dispatchQueue) - // Disable global SIGPIPE handler so that the app doesn't crash - setNoSigPipe(socket: newSocket) + // Set up cancel handler + newResponseSource.setCancelHandler { + // debugPrint("Closing UDP socket") + let UDPSocket = Int32(newResponseSource.handle) + shutdown(UDPSocket, SHUT_RDWR) + close(UDPSocket) + } - // Set up a dispatch source - let newResponseSource = DispatchSource.makeReadSource(fileDescriptor: newSocket, queue: dispatchQueue) + // Set up event handler (gets called when data arrives at the UDP socket) + newResponseSource.setEventHandler { [unowned self] in + guard let source = self.responseSource else { return } - // Set up cancel handler - newResponseSource.setCancelHandler { - // debugPrint("Closing UDP socket") - let UDPSocket = Int32(newResponseSource.handle) - shutdown(UDPSocket, SHUT_RDWR) - close(UDPSocket) - } - - // Set up event handler (gets called when data arrives at the UDP socket) - newResponseSource.setEventHandler { [unowned self] in - guard let source = self.responseSource else { return } - - var socketAddress = sockaddr_storage() - var socketAddressLength = socklen_t(MemoryLayout.size) - let response = [UInt8](repeating: 0, count: 4096) - let UDPSocket = Int32(source.handle) + var socketAddress = sockaddr_storage() + var socketAddressLength = socklen_t(MemoryLayout.size) + let response = [UInt8](repeating: 0, count: 4096) + let UDPSocket = Int32(source.handle) let pointer = UnsafeMutablePointer<[UInt8]>.allocate(capacity: response.capacity) pointer.initialize(to: response) - let bytesRead = withUnsafeMutablePointer(to: &socketAddress) { - recvfrom(UDPSocket, pointer, response.count, 0, - UnsafeMutableRawPointer($0).bindMemory(to: sockaddr.self, capacity: 1), &socketAddressLength) - } + let bytesRead = withUnsafeMutablePointer(to: &socketAddress) { + recvfrom(UDPSocket, pointer, response.count, 0, + UnsafeMutableRawPointer($0).bindMemory(to: sockaddr.self, capacity: 1), &socketAddressLength) + } - do { - guard bytesRead > 0 else { - self.closeConnection() - if bytesRead == 0 { - debugPrint("recvfrom returned EOF") - throw ConnectionError.receivedEndOfFile - } else { - if let errorString = String(validatingUTF8: strerror(errno)) { - debugPrint("recvfrom failed: \(errorString)") - } - throw ConnectionError.receiveFailed(code: errno) - } - } + do { + guard bytesRead > 0 else { + self.closeConnection() + if bytesRead == 0 { + debugPrint("recvfrom returned EOF") + throw ConnectionError.receivedEndOfFile + } else { + if let errorString = String(validatingUTF8: strerror(errno)) { + debugPrint("recvfrom failed: \(errorString)") + } + throw ConnectionError.receiveFailed(code: errno) + } + } - guard let endpoint = withUnsafePointer(to: &socketAddress, - { - self - .getEndpointFromSocketAddress(socketAddressPointer: UnsafeRawPointer($0) - .bindMemory(to: sockaddr.self, capacity: 1)) }) - else { - // debugPrint("Failed to get the address and port from the socket address received from recvfrom") - self.closeConnection() - return - } + guard let endpoint = withUnsafePointer(to: &socketAddress, + { + self + .getEndpointFromSocketAddress(socketAddressPointer: UnsafeRawPointer($0) + .bindMemory(to: sockaddr.self, capacity: 1)) }) + else { + // debugPrint("Failed to get the address and port from the socket address received from recvfrom") + self.closeConnection() + return + } - // debugPrint("UDP connection received \(bytesRead) bytes from \(endpoint.host):\(endpoint.port)") + // debugPrint("UDP connection received \(bytesRead) bytes from \(endpoint.host):\(endpoint.port)") - let responseBytes = Data(response[0 ..< bytesRead]) + let responseBytes = Data(response[0 ..< bytesRead]) - // Handle response - self.handler?(endpoint.host, endpoint.port, responseBytes) - } catch { - if let error = error as? ConnectionError { - self.errorHandler?(error) - } else { - self.errorHandler?(ConnectionError.underlying(error: error)) - } - } - } + // Handle response + self.handler?(endpoint.host, endpoint.port, responseBytes) + } catch { + if let error = error as? ConnectionError { + self.errorHandler?(error) + } else { + self.errorHandler?(ConnectionError.underlying(error: error)) + } + } + } - newResponseSource.resume() - responseSource = newResponseSource - } + newResponseSource.resume() + responseSource = newResponseSource + } - /// Send broadcast message. - /// - /// - Parameter message: Message to send via broadcast. - /// - Throws: Throws a `ConnectionError` if an error occurs. - open func sendBroadcast(_ message: String) throws { - guard let data = message.data(using: .utf8) else { throw ConnectionError.messageEncodingFailed } - try sendBroadcast(data) - } + /// Send broadcast message. + /// + /// - Parameter message: Message to send via broadcast. + /// - Throws: Throws a `ConnectionError` if an error occurs. + open func sendBroadcast(_ message: String) throws { + guard let data = message.data(using: .utf8) else { throw ConnectionError.messageEncodingFailed } + try sendBroadcast(data) + } - /// Send broadcast data. - /// - /// - Parameter data: Data to send via broadcast. - /// - Throws: Throws a `ConnectionError` if an error occurs. - open func sendBroadcast(_ data: Data) throws { - if responseSource == nil { - try createSocket() - } + /// Send broadcast data. + /// + /// - Parameter data: Data to send via broadcast. + /// - Throws: Throws a `ConnectionError` if an error occurs. + open func sendBroadcast(_ data: Data) throws { + if responseSource == nil { + try createSocket() + } - guard let source = responseSource else { return } - let UDPSocket = Int32(source.handle) - let socketLength = socklen_t(address.sin_len) - try data.withUnsafeBytes { broadcastMessage in - let broadcastMessageLength = data.count - let sent = withUnsafeMutablePointer(to: &address) { pointer -> Int in - let memory = UnsafeRawPointer(pointer).bindMemory(to: sockaddr.self, capacity: 1) - return sendto(UDPSocket, broadcastMessage.baseAddress, broadcastMessageLength, 0, memory, socketLength) - } + guard let source = responseSource else { return } + let UDPSocket = Int32(source.handle) + let socketLength = socklen_t(address.sin_len) + try data.withUnsafeBytes { broadcastMessage in + let broadcastMessageLength = data.count + let sent = withUnsafeMutablePointer(to: &address) { pointer -> Int in + let memory = UnsafeRawPointer(pointer).bindMemory(to: sockaddr.self, capacity: 1) + return sendto(UDPSocket, broadcastMessage.baseAddress, broadcastMessageLength, 0, memory, socketLength) + } - guard sent > 0 else { - closeConnection() - throw ConnectionError.sendingMessageFailed(code: errno) - } - } - } + guard sent > 0 else { + closeConnection() + throw ConnectionError.sendingMessageFailed(code: errno) + } + } + } - /// Close the connection. - /// - /// - Parameter reopen: Automatically reopens the connection if true. Defaults to true. - open func closeConnection(reopen: Bool = true) { - if let source = responseSource { - source.cancel() - responseSource = nil - } - if shouldBeBound && reopen { - dispatchQueue.async { - do { - try self.createSocket() - } catch { - self.errorHandler?(ConnectionError.reopeningSocketFailed(error: error)) - } - } - } - } + /// Close the connection. + /// + /// - Parameter reopen: Automatically reopens the connection if true. Defaults to true. + open func closeConnection(reopen: Bool = true) { + if let source = responseSource { + source.cancel() + responseSource = nil + } + if shouldBeBound, reopen { + dispatchQueue.async { + do { + try self.createSocket() + } catch { + self.errorHandler?(ConnectionError.reopeningSocketFailed(error: error)) + } + } + } + } - // MARK: - Helper + // MARK: - Helper - /// Convert a sockaddr structure into an IP address string and port. - /// - /// - Parameter socketAddressPointer: socketAddressPointer: Pointer to a socket address. - /// - Returns: Returns a tuple of the host IP address and the port in the socket address given. - func getEndpointFromSocketAddress(socketAddressPointer: UnsafePointer) -> (host: String, port: Int)? { - let socketAddress = UnsafePointer(socketAddressPointer).pointee + /// Convert a sockaddr structure into an IP address string and port. + /// + /// - Parameter socketAddressPointer: socketAddressPointer: Pointer to a socket address. + /// - Returns: Returns a tuple of the host IP address and the port in the socket address given. + func getEndpointFromSocketAddress(socketAddressPointer: UnsafePointer) -> (host: String, port: Int)? { + let socketAddress = UnsafePointer(socketAddressPointer).pointee - switch Int32(socketAddress.sa_family) { - case AF_INET: - var socketAddressInet = UnsafeRawPointer(socketAddressPointer).load(as: sockaddr_in.self) - let length = Int(INET_ADDRSTRLEN) + 2 - var buffer = [CChar](repeating: 0, count: length) - let hostCString = inet_ntop(AF_INET, &socketAddressInet.sin_addr, &buffer, socklen_t(length)) - let port = Int(UInt16(socketAddressInet.sin_port).byteSwapped) - return (String(cString: hostCString!), port) + switch Int32(socketAddress.sa_family) { + case AF_INET: + var socketAddressInet = UnsafeRawPointer(socketAddressPointer).load(as: sockaddr_in.self) + let length = Int(INET_ADDRSTRLEN) + 2 + var buffer = [CChar](repeating: 0, count: length) + let hostCString = inet_ntop(AF_INET, &socketAddressInet.sin_addr, &buffer, socklen_t(length)) + let port = Int(UInt16(socketAddressInet.sin_port).byteSwapped) + return (String(cString: hostCString!), port) - case AF_INET6: - var socketAddressInet6 = UnsafeRawPointer(socketAddressPointer).load(as: sockaddr_in6.self) - let length = Int(INET6_ADDRSTRLEN) + 2 - var buffer = [CChar](repeating: 0, count: length) - let hostCString = inet_ntop(AF_INET6, &socketAddressInet6.sin6_addr, &buffer, socklen_t(length)) - let port = Int(UInt16(socketAddressInet6.sin6_port).byteSwapped) - return (String(cString: hostCString!), port) + case AF_INET6: + var socketAddressInet6 = UnsafeRawPointer(socketAddressPointer).load(as: sockaddr_in6.self) + let length = Int(INET6_ADDRSTRLEN) + 2 + var buffer = [CChar](repeating: 0, count: length) + let hostCString = inet_ntop(AF_INET6, &socketAddressInet6.sin6_addr, &buffer, socklen_t(length)) + let port = Int(UInt16(socketAddressInet6.sin6_port).byteSwapped) + return (String(cString: hostCString!), port) - default: - return nil - } - } + default: + return nil + } + } - // MARK: - Private + // MARK: - Private - /// Prevents crashes when blocking calls are pending and the app is paused (via Home button). - /// - /// - Parameter socket: The socket for which the signal should be disabled. - fileprivate func setNoSigPipe(socket: CInt) { - var no_sig_pipe: Int32 = 1 - setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout.size)) - } + /// Prevents crashes when blocking calls are pending and the app is paused (via Home button). + /// + /// - Parameter socket: The socket for which the signal should be disabled. + fileprivate func setNoSigPipe(socket: CInt) { + var no_sig_pipe: Int32 = 1 + setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout.size)) + } - fileprivate class func htonsPort(port: in_port_t) -> in_port_t { - let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian - return isLittleEndian ? _OSSwapInt16(port) : port - } + fileprivate class func htonsPort(port: in_port_t) -> in_port_t { + let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian + return isLittleEndian ? _OSSwapInt16(port) : port + } - fileprivate class func ntohs(value: CUnsignedShort) -> CUnsignedShort { - (value << 8) + (value >> 8) - } + fileprivate class func ntohs(value: CUnsignedShort) -> CUnsignedShort { + (value << 8) + (value >> 8) + } } // Created by Gunter Hager on 25.03.19. // Copyright © 2019 Gunter Hager. All rights reserved. // public extension UDPBroadcastConnection { + enum ConnectionError: Error { + // Creating socket + case createSocketFailed + case enableBroadcastFailed + case bindSocketFailed - enum ConnectionError: Error { - // Creating socket - case createSocketFailed - case enableBroadcastFailed - case bindSocketFailed + // Sending message + case messageEncodingFailed + case sendingMessageFailed(code: Int32) - // Sending message - case messageEncodingFailed - case sendingMessageFailed(code: Int32) + // Receiving data + case receivedEndOfFile + case receiveFailed(code: Int32) - // Receiving data - case receivedEndOfFile - case receiveFailed(code: Int32) + // Closing socket + case reopeningSocketFailed(error: Error) - // Closing socket - case reopeningSocketFailed(error: Error) - - // Underlying - case underlying(error: Error) - } + // Underlying + case underlying(error: Error) + } } diff --git a/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift b/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift index ffe7a4b1..1e6c9660 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift @@ -11,356 +11,352 @@ import SwiftUI // TODO: Needs replacement/reworking struct SmallMediaStreamSelectionView: View { + enum Layer: Hashable { + case subtitles + case audio + case playbackSpeed + case chapters + } - enum Layer: Hashable { - case subtitles - case audio - case playbackSpeed - case chapters - } + enum MediaSection: Hashable { + case titles + case items + } - enum MediaSection: Hashable { - case titles - case items - } + @ObservedObject + var viewModel: VideoPlayerViewModel + private let chapterImages: [URL] - @ObservedObject - var viewModel: VideoPlayerViewModel - private let chapterImages: [URL] + @State + private var updateFocusedLayer: Layer = .subtitles + @State + private var lastFocusedLayer: Layer = .subtitles - @State - private var updateFocusedLayer: Layer = .subtitles - @State - private var lastFocusedLayer: Layer = .subtitles + @FocusState + private var subtitlesFocused: Bool + @FocusState + private var audioFocused: Bool + @FocusState + private var playbackSpeedFocused: Bool + @FocusState + private var chaptersFocused: Bool + @FocusState + private var focusedSection: MediaSection? + @FocusState + private var focusedLayer: Layer? { + willSet { + updateFocusedLayer = newValue! - @FocusState - private var subtitlesFocused: Bool - @FocusState - private var audioFocused: Bool - @FocusState - private var playbackSpeedFocused: Bool - @FocusState - private var chaptersFocused: Bool - @FocusState - private var focusedSection: MediaSection? - @FocusState - private var focusedLayer: Layer? { - willSet { - updateFocusedLayer = newValue! + if focusedSection == .titles { + lastFocusedLayer = newValue! + } + } + } - if focusedSection == .titles { - lastFocusedLayer = newValue! - } - } - } + init(viewModel: VideoPlayerViewModel) { + self.viewModel = viewModel + self.chapterImages = viewModel.item.getChapterImage(maxWidth: 500) + } - init(viewModel: VideoPlayerViewModel) { - self.viewModel = viewModel - self.chapterImages = viewModel.item.getChapterImage(maxWidth: 500) - } + var body: some View { + ZStack(alignment: .bottom) { + LinearGradient(gradient: Gradient(colors: [.clear, .black.opacity(0.8), .black]), + startPoint: .top, + endPoint: .bottom) + .ignoresSafeArea() + .frame(height: 300) - var body: some View { - ZStack(alignment: .bottom) { - LinearGradient(gradient: Gradient(colors: [.clear, .black.opacity(0.8), .black]), - startPoint: .top, - endPoint: .bottom) - .ignoresSafeArea() - .frame(height: 300) + VStack { + Spacer() - VStack { + HStack { + // MARK: Subtitle Header - Spacer() + Button { + updateFocusedLayer = .subtitles + focusedLayer = .subtitles + } label: { + if updateFocusedLayer == .subtitles { + HStack(spacing: 15) { + Image(systemName: "captions.bubble") + L10n.subtitles.text + } + .padding() + .background(Color.white) + .foregroundColor(.black) + } else { + HStack(spacing: 15) { + Image(systemName: "captions.bubble") + L10n.subtitles.text + } + .padding() + } + } + .buttonStyle(PlainButtonStyle()) + .background(Color.clear) + .focused($focusedLayer, equals: .subtitles) + .focused($subtitlesFocused) + .onChange(of: subtitlesFocused) { isFocused in + if isFocused { + focusedLayer = .subtitles + } + } - HStack { + // MARK: Audio Header - // MARK: Subtitle Header + Button { + updateFocusedLayer = .audio + focusedLayer = .audio + } label: { + if updateFocusedLayer == .audio { + HStack(spacing: 15) { + Image(systemName: "speaker.wave.3") + L10n.audio.text + } + .padding() + .background(Color.white) + .foregroundColor(.black) + } else { + HStack(spacing: 15) { + Image(systemName: "speaker.wave.3") + L10n.audio.text + } + .padding() + } + } + .buttonStyle(PlainButtonStyle()) + .background(Color.clear) + .focused($focusedLayer, equals: .audio) + .focused($audioFocused) + .onChange(of: audioFocused) { isFocused in + if isFocused { + focusedLayer = .audio + } + } - Button { - updateFocusedLayer = .subtitles - focusedLayer = .subtitles - } label: { - if updateFocusedLayer == .subtitles { - HStack(spacing: 15) { - Image(systemName: "captions.bubble") - L10n.subtitles.text - } - .padding() - .background(Color.white) - .foregroundColor(.black) - } else { - HStack(spacing: 15) { - Image(systemName: "captions.bubble") - L10n.subtitles.text - } - .padding() - } - } - .buttonStyle(PlainButtonStyle()) - .background(Color.clear) - .focused($focusedLayer, equals: .subtitles) - .focused($subtitlesFocused) - .onChange(of: subtitlesFocused) { isFocused in - if isFocused { - focusedLayer = .subtitles - } - } + // MARK: Playback Speed Header - // MARK: Audio Header + Button { + updateFocusedLayer = .playbackSpeed + focusedLayer = .playbackSpeed + } label: { + if updateFocusedLayer == .playbackSpeed { + HStack(spacing: 15) { + Image(systemName: "speedometer") + L10n.playbackSpeed.text + } + .padding() + .background(Color.white) + .foregroundColor(.black) + } else { + HStack(spacing: 15) { + Image(systemName: "speedometer") + L10n.playbackSpeed.text + } + .padding() + } + } + .buttonStyle(PlainButtonStyle()) + .background(Color.clear) + .focused($focusedLayer, equals: .playbackSpeed) + .focused($playbackSpeedFocused) + .onChange(of: playbackSpeedFocused) { isFocused in + if isFocused { + focusedLayer = .playbackSpeed + } + } - Button { - updateFocusedLayer = .audio - focusedLayer = .audio - } label: { - if updateFocusedLayer == .audio { - HStack(spacing: 15) { - Image(systemName: "speaker.wave.3") - L10n.audio.text - } - .padding() - .background(Color.white) - .foregroundColor(.black) - } else { - HStack(spacing: 15) { - Image(systemName: "speaker.wave.3") - L10n.audio.text - } - .padding() - } - } - .buttonStyle(PlainButtonStyle()) - .background(Color.clear) - .focused($focusedLayer, equals: .audio) - .focused($audioFocused) - .onChange(of: audioFocused) { isFocused in - if isFocused { - focusedLayer = .audio - } - } + // MARK: Chapters Header - // MARK: Playback Speed Header + if !viewModel.chapters.isEmpty { + Button { + updateFocusedLayer = .chapters + focusedLayer = .chapters + } label: { + if updateFocusedLayer == .chapters { + HStack(spacing: 15) { + Image(systemName: "list.dash") + L10n.chapters.text + } + .padding() + .background(Color.white) + .foregroundColor(.black) + } else { + HStack(spacing: 15) { + Image(systemName: "list.dash") + L10n.chapters.text + } + .padding() + } + } + .buttonStyle(PlainButtonStyle()) + .background(Color.clear) + .focused($focusedLayer, equals: .chapters) + .focused($chaptersFocused) + .onChange(of: chaptersFocused) { isFocused in + if isFocused { + focusedLayer = .chapters + } + } + } - Button { - updateFocusedLayer = .playbackSpeed - focusedLayer = .playbackSpeed - } label: { - if updateFocusedLayer == .playbackSpeed { - HStack(spacing: 15) { - Image(systemName: "speedometer") - L10n.playbackSpeed.text - } - .padding() - .background(Color.white) - .foregroundColor(.black) - } else { - HStack(spacing: 15) { - Image(systemName: "speedometer") - L10n.playbackSpeed.text - } - .padding() - } - } - .buttonStyle(PlainButtonStyle()) - .background(Color.clear) - .focused($focusedLayer, equals: .playbackSpeed) - .focused($playbackSpeedFocused) - .onChange(of: playbackSpeedFocused) { isFocused in - if isFocused { - focusedLayer = .playbackSpeed - } - } + Spacer() + } + .padding() + .focusSection() + .focused($focusedSection, equals: .titles) + .onChange(of: focusedSection) { _ in + if focusedSection == .titles { + if lastFocusedLayer == .subtitles { + subtitlesFocused = true + } else if lastFocusedLayer == .audio { + audioFocused = true + } else if lastFocusedLayer == .playbackSpeed { + playbackSpeedFocused = true + } + } + } - // MARK: Chapters Header + if updateFocusedLayer == .subtitles, lastFocusedLayer == .subtitles { + // MARK: Subtitles - if !viewModel.chapters.isEmpty { - Button { - updateFocusedLayer = .chapters - focusedLayer = .chapters - } label: { - if updateFocusedLayer == .chapters { - HStack(spacing: 15) { - Image(systemName: "list.dash") - L10n.chapters.text - } - .padding() - .background(Color.white) - .foregroundColor(.black) - } else { - HStack(spacing: 15) { - Image(systemName: "list.dash") - L10n.chapters.text - } - .padding() - } - } - .buttonStyle(PlainButtonStyle()) - .background(Color.clear) - .focused($focusedLayer, equals: .chapters) - .focused($chaptersFocused) - .onChange(of: chaptersFocused) { isFocused in - if isFocused { - focusedLayer = .chapters - } - } - } + subtitleMenuView + } else if updateFocusedLayer == .audio, lastFocusedLayer == .audio { + // MARK: Audio - Spacer() - } - .padding() - .focusSection() - .focused($focusedSection, equals: .titles) - .onChange(of: focusedSection) { _ in - if focusedSection == .titles { - if lastFocusedLayer == .subtitles { - subtitlesFocused = true - } else if lastFocusedLayer == .audio { - audioFocused = true - } else if lastFocusedLayer == .playbackSpeed { - playbackSpeedFocused = true - } - } - } + audioMenuView + } else if updateFocusedLayer == .playbackSpeed, lastFocusedLayer == .playbackSpeed { + // MARK: Playback Speed - if updateFocusedLayer == .subtitles && lastFocusedLayer == .subtitles { - // MARK: Subtitles + playbackSpeedMenuView + } else if updateFocusedLayer == .chapters, lastFocusedLayer == .chapters { + // MARK: Chapters - subtitleMenuView - } else if updateFocusedLayer == .audio && lastFocusedLayer == .audio { - // MARK: Audio + chaptersMenuView + } + } + } + } - audioMenuView - } else if updateFocusedLayer == .playbackSpeed && lastFocusedLayer == .playbackSpeed { - // MARK: Playback Speed + @ViewBuilder + private var subtitleMenuView: some View { + ScrollView(.horizontal) { + HStack { + if viewModel.subtitleStreams.isEmpty { + Button {} label: { + L10n.none.text + } + } else { + ForEach(viewModel.subtitleStreams, id: \.self) { subtitleStream in + Button { + viewModel.selectedSubtitleStreamIndex = subtitleStream.index ?? -1 + } label: { + if subtitleStream.index == viewModel.selectedSubtitleStreamIndex { + Label(subtitleStream.displayTitle ?? L10n.noTitle, systemImage: "checkmark") + } else { + Text(subtitleStream.displayTitle ?? L10n.noTitle) + } + } + } + } + } + .padding(.vertical) + .focusSection() + .focused($focusedSection, equals: .items) + } + } - playbackSpeedMenuView - } else if updateFocusedLayer == .chapters && lastFocusedLayer == .chapters { - // MARK: Chapters + @ViewBuilder + private var audioMenuView: some View { + ScrollView(.horizontal) { + HStack { + if viewModel.audioStreams.isEmpty { + Button {} label: { + Text("None") + } + } else { + ForEach(viewModel.audioStreams, id: \.self) { audioStream in + Button { + viewModel.selectedAudioStreamIndex = audioStream.index ?? -1 + } label: { + if audioStream.index == viewModel.selectedAudioStreamIndex { + Label(audioStream.displayTitle ?? L10n.noTitle, systemImage: "checkmark") + } else { + Text(audioStream.displayTitle ?? L10n.noTitle) + } + } + } + } + } + .padding(.vertical) + .focusSection() + .focused($focusedSection, equals: .items) + } + } - chaptersMenuView - } - } - } - } + @ViewBuilder + private var playbackSpeedMenuView: some View { + ScrollView(.horizontal) { + HStack { + ForEach(PlaybackSpeed.allCases, id: \.self) { playbackSpeed in + Button { + viewModel.playbackSpeed = playbackSpeed + } label: { + if playbackSpeed == viewModel.playbackSpeed { + Label(playbackSpeed.displayTitle, systemImage: "checkmark") + } else { + Text(playbackSpeed.displayTitle) + } + } + } + } + .padding(.vertical) + .focusSection() + .focused($focusedSection, equals: .items) + } + } - @ViewBuilder - private var subtitleMenuView: some View { - ScrollView(.horizontal) { - HStack { - if viewModel.subtitleStreams.isEmpty { - Button {} label: { - L10n.none.text - } - } else { - ForEach(viewModel.subtitleStreams, id: \.self) { subtitleStream in - Button { - viewModel.selectedSubtitleStreamIndex = subtitleStream.index ?? -1 - } label: { - if subtitleStream.index == viewModel.selectedSubtitleStreamIndex { - Label(subtitleStream.displayTitle ?? L10n.noTitle, systemImage: "checkmark") - } else { - Text(subtitleStream.displayTitle ?? L10n.noTitle) - } - } - } - } - } - .padding(.vertical) - .focusSection() - .focused($focusedSection, equals: .items) - } - } - - @ViewBuilder - private var audioMenuView: some View { - ScrollView(.horizontal) { - HStack { - if viewModel.audioStreams.isEmpty { - Button {} label: { - Text("None") - } - } else { - ForEach(viewModel.audioStreams, id: \.self) { audioStream in - Button { - viewModel.selectedAudioStreamIndex = audioStream.index ?? -1 - } label: { - if audioStream.index == viewModel.selectedAudioStreamIndex { - Label(audioStream.displayTitle ?? L10n.noTitle, systemImage: "checkmark") - } else { - Text(audioStream.displayTitle ?? L10n.noTitle) - } - } - } - } - } - .padding(.vertical) - .focusSection() - .focused($focusedSection, equals: .items) - } - } - - @ViewBuilder - private var playbackSpeedMenuView: some View { - ScrollView(.horizontal) { - HStack { - ForEach(PlaybackSpeed.allCases, id: \.self) { playbackSpeed in - Button { - viewModel.playbackSpeed = playbackSpeed - } label: { - if playbackSpeed == viewModel.playbackSpeed { - Label(playbackSpeed.displayTitle, systemImage: "checkmark") - } else { - Text(playbackSpeed.displayTitle) - } - } - } - } - .padding(.vertical) - .focusSection() - .focused($focusedSection, equals: .items) - } - } - - @ViewBuilder - private var chaptersMenuView: some View { - ScrollView(.horizontal, showsIndicators: false) { - ScrollViewReader { reader in - HStack { + @ViewBuilder + private var chaptersMenuView: some View { + ScrollView(.horizontal, showsIndicators: false) { + ScrollViewReader { reader in + HStack { ForEach(0 ..< viewModel.chapters.count, id: \.self) { chapterIndex in - VStack(alignment: .leading) { - Button { - viewModel.playerOverlayDelegate?.didSelectChapter(viewModel.chapters[chapterIndex]) - } label: { - ImageView(chapterImages[chapterIndex]) - .cornerRadius(10) - .frame(width: 350, height: 210) - } - .buttonStyle(CardButtonStyle()) + VStack(alignment: .leading) { + Button { + viewModel.playerOverlayDelegate?.didSelectChapter(viewModel.chapters[chapterIndex]) + } label: { + ImageView(chapterImages[chapterIndex]) + .cornerRadius(10) + .frame(width: 350, height: 210) + } + .buttonStyle(CardButtonStyle()) - VStack(alignment: .leading, spacing: 5) { + VStack(alignment: .leading, spacing: 5) { + Text(viewModel.chapters[chapterIndex].name ?? L10n.noTitle) + .font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.white) - Text(viewModel.chapters[chapterIndex].name ?? L10n.noTitle) - .font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(.white) - - Text(viewModel.chapters[chapterIndex].timestampLabel) - .font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(Color(UIColor.systemBlue)) - .padding(.vertical, 2) - .padding(.horizontal, 4) - .background { - Color(UIColor.darkGray).opacity(0.2).cornerRadius(4) - } - } - } - .id(viewModel.chapters[chapterIndex]) - } - } - .padding(.top) - .onAppear { - reader.scrollTo(viewModel.currentChapter) - } - } - } - } + Text(viewModel.chapters[chapterIndex].timestampLabel) + .font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(Color(UIColor.systemBlue)) + .padding(.vertical, 2) + .padding(.horizontal, 4) + .background { + Color(UIColor.darkGray).opacity(0.2).cornerRadius(4) + } + } + } + .id(viewModel.chapters[chapterIndex]) + } + } + .padding(.top) + .onAppear { + reader.scrollTo(viewModel.currentChapter) + } + } + } + } } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 09fdcef5..537b1932 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -191,6 +191,7 @@ 62666E2127E501E400EC0ECD /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2027E501E400EC0ECD /* CoreVideo.framework */; }; 62666E2327E501EB00EC0ECD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2227E501EB00EC0ECD /* Foundation.framework */; }; 62666E2427E501F300EC0ECD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5362E4BC267D40D8000E2F71 /* Foundation.framework */; }; + 62666E2A27E5020A00EC0ECD /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2927E5020A00EC0ECD /* OpenGLES.framework */; }; 62666E2C27E5021000EC0ECD /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2B27E5021000EC0ECD /* QuartzCore.framework */; }; 62666E2E27E5021400EC0ECD /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2D27E5021400EC0ECD /* Security.framework */; }; 62666E3027E5021800EC0ECD /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62666E2F27E5021800EC0ECD /* VideoToolbox.framework */; }; @@ -894,6 +895,7 @@ 62666DFA27E5013700EC0ECD /* TVVLCKit.xcframework in Frameworks */, 62666E3227E5021E00EC0ECD /* UIKit.framework in Frameworks */, E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */, + 62666E2A27E5020A00EC0ECD /* OpenGLES.framework in Frameworks */, E1002B6B2793E36600E47059 /* Algorithms in Frameworks */, 62666E1D27E501DB00EC0ECD /* CoreMedia.framework in Frameworks */, 62666E3027E5021800EC0ECD /* VideoToolbox.framework in Frameworks */, From 0ca924c8f205c1394aaabf3408a871a89d9eaa9e Mon Sep 17 00:00:00 2001 From: Joe Diragi Date: Sun, 1 May 2022 15:23:52 -0400 Subject: [PATCH 24/25] Updates swiftformat --- .../UDPBroadCastConnection.swift | 485 +++++++------ .../Overlays/SmallMenuOverlay.swift | 644 +++++++++--------- 2 files changed, 566 insertions(+), 563 deletions(-) diff --git a/Shared/ServerDiscovery/UDPBroadCastConnection.swift b/Shared/ServerDiscovery/UDPBroadCastConnection.swift index abb8677a..8ce69534 100644 --- a/Shared/ServerDiscovery/UDPBroadCastConnection.swift +++ b/Shared/ServerDiscovery/UDPBroadCastConnection.swift @@ -16,298 +16,297 @@ let INADDR_BROADCAST = in_addr(s_addr: 0xFFFF_FFFF) /// An object representing the UDP broadcast connection. Uses a dispatch source to handle the incoming traffic on the UDP socket. open class UDPBroadcastConnection { - // MARK: Properties + // MARK: Properties - /// The address of the UDP socket. - var address: sockaddr_in + /// The address of the UDP socket. + var address: sockaddr_in - /// Type of a closure that handles incoming UDP packets. - public typealias ReceiveHandler = (_ ipAddress: String, _ port: Int, _ response: Data) -> Void - /// Closure that handles incoming UDP packets. - var handler: ReceiveHandler? + /// Type of a closure that handles incoming UDP packets. + public typealias ReceiveHandler = (_ ipAddress: String, _ port: Int, _ response: Data) -> Void + /// Closure that handles incoming UDP packets. + var handler: ReceiveHandler? - /// Type of a closure that handles errors that were encountered during receiving UDP packets. - public typealias ErrorHandler = (_ error: ConnectionError) -> Void - /// Closure that handles errors that were encountered during receiving UDP packets. - var errorHandler: ErrorHandler? + /// Type of a closure that handles errors that were encountered during receiving UDP packets. + public typealias ErrorHandler = (_ error: ConnectionError) -> Void + /// Closure that handles errors that were encountered during receiving UDP packets. + var errorHandler: ErrorHandler? - /// A dispatch source for reading data from the UDP socket. - var responseSource: DispatchSourceRead? + /// A dispatch source for reading data from the UDP socket. + var responseSource: DispatchSourceRead? - /// The dispatch queue to run responseSource & reconnection on - var dispatchQueue = DispatchQueue.main + /// The dispatch queue to run responseSource & reconnection on + var dispatchQueue = DispatchQueue.main - /// Bind to port to start listening without first sending a message - var shouldBeBound: Bool = false + /// Bind to port to start listening without first sending a message + var shouldBeBound: Bool = false - // MARK: Initializers + // MARK: Initializers - /// Initializes the UDP connection with the correct port address. + /// Initializes the UDP connection with the correct port address. - /// - Note: This doesn't open a socket! The socket is opened transparently as needed when sending broadcast messages. If you want to open a socket immediately, use the `bindIt` parameter. This will also try to reopen the socket if it gets closed. - /// - /// - Parameters: - /// - port: Number of the UDP port to use. - /// - bindIt: Opens a port immediately if true, on demand if false. Default is false. - /// - handler: Handler that gets called when data is received. - /// - errorHandler: Handler that gets called when an error occurs. - /// - Throws: Throws a `ConnectionError` if an error occurs. - public init(port: UInt16, bindIt: Bool = false, handler: ReceiveHandler?, errorHandler: ErrorHandler?) throws { - self.address = sockaddr_in(sin_len: __uint8_t(MemoryLayout.size), - sin_family: sa_family_t(AF_INET), - sin_port: UDPBroadcastConnection.htonsPort(port: port), - sin_addr: INADDR_BROADCAST, - sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) + /// - Note: This doesn't open a socket! The socket is opened transparently as needed when sending broadcast messages. If you want to open a socket immediately, use the `bindIt` parameter. This will also try to reopen the socket if it gets closed. + /// + /// - Parameters: + /// - port: Number of the UDP port to use. + /// - bindIt: Opens a port immediately if true, on demand if false. Default is false. + /// - handler: Handler that gets called when data is received. + /// - errorHandler: Handler that gets called when an error occurs. + /// - Throws: Throws a `ConnectionError` if an error occurs. + public init(port: UInt16, bindIt: Bool = false, handler: ReceiveHandler?, errorHandler: ErrorHandler?) throws { + self.address = sockaddr_in(sin_len: __uint8_t(MemoryLayout.size), + sin_family: sa_family_t(AF_INET), + sin_port: UDPBroadcastConnection.htonsPort(port: port), + sin_addr: INADDR_BROADCAST, + sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) - self.handler = handler - self.errorHandler = errorHandler - self.shouldBeBound = bindIt - if bindIt { - try createSocket() - } - } + self.handler = handler + self.errorHandler = errorHandler + self.shouldBeBound = bindIt + if bindIt { + try createSocket() + } + } - deinit { - if responseSource != nil { - responseSource!.cancel() - } - } + deinit { + if responseSource != nil { + responseSource!.cancel() + } + } - // MARK: Interface + // MARK: Interface - /// Create a UDP socket for broadcasting and set up cancel and event handlers - /// - /// - Throws: Throws a `ConnectionError` if an error occurs. - fileprivate func createSocket() throws { - // Create new socket - let newSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) - guard newSocket > 0 else { throw ConnectionError.createSocketFailed } + /// Create a UDP socket for broadcasting and set up cancel and event handlers + /// + /// - Throws: Throws a `ConnectionError` if an error occurs. + fileprivate func createSocket() throws { + // Create new socket + let newSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) + guard newSocket > 0 else { throw ConnectionError.createSocketFailed } - // Enable broadcast on socket - var broadcastEnable = Int32(1) - let ret = setsockopt(newSocket, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, socklen_t(MemoryLayout.size)) - if ret == -1 { - debugPrint("Couldn't enable broadcast on socket") - close(newSocket) - throw ConnectionError.enableBroadcastFailed - } + // Enable broadcast on socket + var broadcastEnable = Int32(1) + let ret = setsockopt(newSocket, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, socklen_t(MemoryLayout.size)) + if ret == -1 { + debugPrint("Couldn't enable broadcast on socket") + close(newSocket) + throw ConnectionError.enableBroadcastFailed + } - // Bind socket if needed - if shouldBeBound { - var saddr = sockaddr(sa_len: 0, sa_family: 0, - sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - address.sin_addr = INADDR_ANY - memcpy(&saddr, &address, MemoryLayout.size) - address.sin_addr = INADDR_BROADCAST - let isBound = bind(newSocket, &saddr, socklen_t(MemoryLayout.size)) - if isBound == -1 { - debugPrint("Couldn't bind socket") - close(newSocket) - throw ConnectionError.bindSocketFailed - } - } + // Bind socket if needed + if shouldBeBound { + var saddr = sockaddr(sa_len: 0, sa_family: 0, + sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + address.sin_addr = INADDR_ANY + memcpy(&saddr, &address, MemoryLayout.size) + address.sin_addr = INADDR_BROADCAST + let isBound = bind(newSocket, &saddr, socklen_t(MemoryLayout.size)) + if isBound == -1 { + debugPrint("Couldn't bind socket") + close(newSocket) + throw ConnectionError.bindSocketFailed + } + } - // Disable global SIGPIPE handler so that the app doesn't crash - setNoSigPipe(socket: newSocket) + // Disable global SIGPIPE handler so that the app doesn't crash + setNoSigPipe(socket: newSocket) - // Set up a dispatch source - let newResponseSource = DispatchSource.makeReadSource(fileDescriptor: newSocket, queue: dispatchQueue) + // Set up a dispatch source + let newResponseSource = DispatchSource.makeReadSource(fileDescriptor: newSocket, queue: dispatchQueue) - // Set up cancel handler - newResponseSource.setCancelHandler { - // debugPrint("Closing UDP socket") - let UDPSocket = Int32(newResponseSource.handle) - shutdown(UDPSocket, SHUT_RDWR) - close(UDPSocket) - } + // Set up cancel handler + newResponseSource.setCancelHandler { + // debugPrint("Closing UDP socket") + let UDPSocket = Int32(newResponseSource.handle) + shutdown(UDPSocket, SHUT_RDWR) + close(UDPSocket) + } - // Set up event handler (gets called when data arrives at the UDP socket) - newResponseSource.setEventHandler { [unowned self] in - guard let source = self.responseSource else { return } + // Set up event handler (gets called when data arrives at the UDP socket) + newResponseSource.setEventHandler { [unowned self] in + guard let source = self.responseSource else { return } - var socketAddress = sockaddr_storage() - var socketAddressLength = socklen_t(MemoryLayout.size) - let response = [UInt8](repeating: 0, count: 4096) - let UDPSocket = Int32(source.handle) - let pointer = UnsafeMutablePointer<[UInt8]>.allocate(capacity: response.capacity) - pointer.initialize(to: response) + var socketAddress = sockaddr_storage() + var socketAddressLength = socklen_t(MemoryLayout.size) + let response = [UInt8](repeating: 0, count: 4096) + let UDPSocket = Int32(source.handle) + let pointer = UnsafeMutablePointer<[UInt8]>.allocate(capacity: response.capacity) + pointer.initialize(to: response) - let bytesRead = withUnsafeMutablePointer(to: &socketAddress) { - recvfrom(UDPSocket, pointer, response.count, 0, - UnsafeMutableRawPointer($0).bindMemory(to: sockaddr.self, capacity: 1), &socketAddressLength) - } + let bytesRead = withUnsafeMutablePointer(to: &socketAddress) { + recvfrom(UDPSocket, pointer, response.count, 0, + UnsafeMutableRawPointer($0).bindMemory(to: sockaddr.self, capacity: 1), &socketAddressLength) + } - do { - guard bytesRead > 0 else { - self.closeConnection() - if bytesRead == 0 { - debugPrint("recvfrom returned EOF") - throw ConnectionError.receivedEndOfFile - } else { - if let errorString = String(validatingUTF8: strerror(errno)) { - debugPrint("recvfrom failed: \(errorString)") - } - throw ConnectionError.receiveFailed(code: errno) - } - } + do { + guard bytesRead > 0 else { + self.closeConnection() + if bytesRead == 0 { + debugPrint("recvfrom returned EOF") + throw ConnectionError.receivedEndOfFile + } else { + if let errorString = String(validatingUTF8: strerror(errno)) { + debugPrint("recvfrom failed: \(errorString)") + } + throw ConnectionError.receiveFailed(code: errno) + } + } - guard let endpoint = withUnsafePointer(to: &socketAddress, - { - self - .getEndpointFromSocketAddress(socketAddressPointer: UnsafeRawPointer($0) - .bindMemory(to: sockaddr.self, capacity: 1)) }) - else { - // debugPrint("Failed to get the address and port from the socket address received from recvfrom") - self.closeConnection() - return - } + guard let endpoint = withUnsafePointer(to: &socketAddress, { + self + .getEndpointFromSocketAddress(socketAddressPointer: UnsafeRawPointer($0) + .bindMemory(to: sockaddr.self, capacity: 1)) }) + else { + // debugPrint("Failed to get the address and port from the socket address received from recvfrom") + self.closeConnection() + return + } - // debugPrint("UDP connection received \(bytesRead) bytes from \(endpoint.host):\(endpoint.port)") + // debugPrint("UDP connection received \(bytesRead) bytes from \(endpoint.host):\(endpoint.port)") - let responseBytes = Data(response[0 ..< bytesRead]) + let responseBytes = Data(response[0 ..< bytesRead]) - // Handle response - self.handler?(endpoint.host, endpoint.port, responseBytes) - } catch { - if let error = error as? ConnectionError { - self.errorHandler?(error) - } else { - self.errorHandler?(ConnectionError.underlying(error: error)) - } - } - } + // Handle response + self.handler?(endpoint.host, endpoint.port, responseBytes) + } catch { + if let error = error as? ConnectionError { + self.errorHandler?(error) + } else { + self.errorHandler?(ConnectionError.underlying(error: error)) + } + } + } - newResponseSource.resume() - responseSource = newResponseSource - } + newResponseSource.resume() + responseSource = newResponseSource + } - /// Send broadcast message. - /// - /// - Parameter message: Message to send via broadcast. - /// - Throws: Throws a `ConnectionError` if an error occurs. - open func sendBroadcast(_ message: String) throws { - guard let data = message.data(using: .utf8) else { throw ConnectionError.messageEncodingFailed } - try sendBroadcast(data) - } + /// Send broadcast message. + /// + /// - Parameter message: Message to send via broadcast. + /// - Throws: Throws a `ConnectionError` if an error occurs. + open func sendBroadcast(_ message: String) throws { + guard let data = message.data(using: .utf8) else { throw ConnectionError.messageEncodingFailed } + try sendBroadcast(data) + } - /// Send broadcast data. - /// - /// - Parameter data: Data to send via broadcast. - /// - Throws: Throws a `ConnectionError` if an error occurs. - open func sendBroadcast(_ data: Data) throws { - if responseSource == nil { - try createSocket() - } + /// Send broadcast data. + /// + /// - Parameter data: Data to send via broadcast. + /// - Throws: Throws a `ConnectionError` if an error occurs. + open func sendBroadcast(_ data: Data) throws { + if responseSource == nil { + try createSocket() + } - guard let source = responseSource else { return } - let UDPSocket = Int32(source.handle) - let socketLength = socklen_t(address.sin_len) - try data.withUnsafeBytes { broadcastMessage in - let broadcastMessageLength = data.count - let sent = withUnsafeMutablePointer(to: &address) { pointer -> Int in - let memory = UnsafeRawPointer(pointer).bindMemory(to: sockaddr.self, capacity: 1) - return sendto(UDPSocket, broadcastMessage.baseAddress, broadcastMessageLength, 0, memory, socketLength) - } + guard let source = responseSource else { return } + let UDPSocket = Int32(source.handle) + let socketLength = socklen_t(address.sin_len) + try data.withUnsafeBytes { broadcastMessage in + let broadcastMessageLength = data.count + let sent = withUnsafeMutablePointer(to: &address) { pointer -> Int in + let memory = UnsafeRawPointer(pointer).bindMemory(to: sockaddr.self, capacity: 1) + return sendto(UDPSocket, broadcastMessage.baseAddress, broadcastMessageLength, 0, memory, socketLength) + } - guard sent > 0 else { - closeConnection() - throw ConnectionError.sendingMessageFailed(code: errno) - } - } - } + guard sent > 0 else { + closeConnection() + throw ConnectionError.sendingMessageFailed(code: errno) + } + } + } - /// Close the connection. - /// - /// - Parameter reopen: Automatically reopens the connection if true. Defaults to true. - open func closeConnection(reopen: Bool = true) { - if let source = responseSource { - source.cancel() - responseSource = nil - } - if shouldBeBound, reopen { - dispatchQueue.async { - do { - try self.createSocket() - } catch { - self.errorHandler?(ConnectionError.reopeningSocketFailed(error: error)) - } - } - } - } + /// Close the connection. + /// + /// - Parameter reopen: Automatically reopens the connection if true. Defaults to true. + open func closeConnection(reopen: Bool = true) { + if let source = responseSource { + source.cancel() + responseSource = nil + } + if shouldBeBound, reopen { + dispatchQueue.async { + do { + try self.createSocket() + } catch { + self.errorHandler?(ConnectionError.reopeningSocketFailed(error: error)) + } + } + } + } - // MARK: - Helper + // MARK: - Helper - /// Convert a sockaddr structure into an IP address string and port. - /// - /// - Parameter socketAddressPointer: socketAddressPointer: Pointer to a socket address. - /// - Returns: Returns a tuple of the host IP address and the port in the socket address given. - func getEndpointFromSocketAddress(socketAddressPointer: UnsafePointer) -> (host: String, port: Int)? { - let socketAddress = UnsafePointer(socketAddressPointer).pointee + /// Convert a sockaddr structure into an IP address string and port. + /// + /// - Parameter socketAddressPointer: socketAddressPointer: Pointer to a socket address. + /// - Returns: Returns a tuple of the host IP address and the port in the socket address given. + func getEndpointFromSocketAddress(socketAddressPointer: UnsafePointer) -> (host: String, port: Int)? { + let socketAddress = UnsafePointer(socketAddressPointer).pointee - switch Int32(socketAddress.sa_family) { - case AF_INET: - var socketAddressInet = UnsafeRawPointer(socketAddressPointer).load(as: sockaddr_in.self) - let length = Int(INET_ADDRSTRLEN) + 2 - var buffer = [CChar](repeating: 0, count: length) - let hostCString = inet_ntop(AF_INET, &socketAddressInet.sin_addr, &buffer, socklen_t(length)) - let port = Int(UInt16(socketAddressInet.sin_port).byteSwapped) - return (String(cString: hostCString!), port) + switch Int32(socketAddress.sa_family) { + case AF_INET: + var socketAddressInet = UnsafeRawPointer(socketAddressPointer).load(as: sockaddr_in.self) + let length = Int(INET_ADDRSTRLEN) + 2 + var buffer = [CChar](repeating: 0, count: length) + let hostCString = inet_ntop(AF_INET, &socketAddressInet.sin_addr, &buffer, socklen_t(length)) + let port = Int(UInt16(socketAddressInet.sin_port).byteSwapped) + return (String(cString: hostCString!), port) - case AF_INET6: - var socketAddressInet6 = UnsafeRawPointer(socketAddressPointer).load(as: sockaddr_in6.self) - let length = Int(INET6_ADDRSTRLEN) + 2 - var buffer = [CChar](repeating: 0, count: length) - let hostCString = inet_ntop(AF_INET6, &socketAddressInet6.sin6_addr, &buffer, socklen_t(length)) - let port = Int(UInt16(socketAddressInet6.sin6_port).byteSwapped) - return (String(cString: hostCString!), port) + case AF_INET6: + var socketAddressInet6 = UnsafeRawPointer(socketAddressPointer).load(as: sockaddr_in6.self) + let length = Int(INET6_ADDRSTRLEN) + 2 + var buffer = [CChar](repeating: 0, count: length) + let hostCString = inet_ntop(AF_INET6, &socketAddressInet6.sin6_addr, &buffer, socklen_t(length)) + let port = Int(UInt16(socketAddressInet6.sin6_port).byteSwapped) + return (String(cString: hostCString!), port) - default: - return nil - } - } + default: + return nil + } + } - // MARK: - Private + // MARK: - Private - /// Prevents crashes when blocking calls are pending and the app is paused (via Home button). - /// - /// - Parameter socket: The socket for which the signal should be disabled. - fileprivate func setNoSigPipe(socket: CInt) { - var no_sig_pipe: Int32 = 1 - setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout.size)) - } + /// Prevents crashes when blocking calls are pending and the app is paused (via Home button). + /// + /// - Parameter socket: The socket for which the signal should be disabled. + fileprivate func setNoSigPipe(socket: CInt) { + var no_sig_pipe: Int32 = 1 + setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout.size)) + } - fileprivate class func htonsPort(port: in_port_t) -> in_port_t { - let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian - return isLittleEndian ? _OSSwapInt16(port) : port - } + fileprivate class func htonsPort(port: in_port_t) -> in_port_t { + let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian + return isLittleEndian ? _OSSwapInt16(port) : port + } - fileprivate class func ntohs(value: CUnsignedShort) -> CUnsignedShort { - (value << 8) + (value >> 8) - } + fileprivate class func ntohs(value: CUnsignedShort) -> CUnsignedShort { + (value << 8) + (value >> 8) + } } // Created by Gunter Hager on 25.03.19. // Copyright © 2019 Gunter Hager. All rights reserved. // public extension UDPBroadcastConnection { - enum ConnectionError: Error { - // Creating socket - case createSocketFailed - case enableBroadcastFailed - case bindSocketFailed + enum ConnectionError: Error { + // Creating socket + case createSocketFailed + case enableBroadcastFailed + case bindSocketFailed - // Sending message - case messageEncodingFailed - case sendingMessageFailed(code: Int32) + // Sending message + case messageEncodingFailed + case sendingMessageFailed(code: Int32) - // Receiving data - case receivedEndOfFile - case receiveFailed(code: Int32) + // Receiving data + case receivedEndOfFile + case receiveFailed(code: Int32) - // Closing socket - case reopeningSocketFailed(error: Error) + // Closing socket + case reopeningSocketFailed(error: Error) - // Underlying - case underlying(error: Error) - } + // Underlying + case underlying(error: Error) + } } diff --git a/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift b/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift index 1e6c9660..1bebee07 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift @@ -11,352 +11,356 @@ import SwiftUI // TODO: Needs replacement/reworking struct SmallMediaStreamSelectionView: View { - enum Layer: Hashable { - case subtitles - case audio - case playbackSpeed - case chapters - } - enum MediaSection: Hashable { - case titles - case items - } + enum Layer: Hashable { + case subtitles + case audio + case playbackSpeed + case chapters + } - @ObservedObject - var viewModel: VideoPlayerViewModel - private let chapterImages: [URL] + enum MediaSection: Hashable { + case titles + case items + } - @State - private var updateFocusedLayer: Layer = .subtitles - @State - private var lastFocusedLayer: Layer = .subtitles + @ObservedObject + var viewModel: VideoPlayerViewModel + private let chapterImages: [URL] - @FocusState - private var subtitlesFocused: Bool - @FocusState - private var audioFocused: Bool - @FocusState - private var playbackSpeedFocused: Bool - @FocusState - private var chaptersFocused: Bool - @FocusState - private var focusedSection: MediaSection? - @FocusState - private var focusedLayer: Layer? { - willSet { - updateFocusedLayer = newValue! + @State + private var updateFocusedLayer: Layer = .subtitles + @State + private var lastFocusedLayer: Layer = .subtitles - if focusedSection == .titles { - lastFocusedLayer = newValue! - } - } - } + @FocusState + private var subtitlesFocused: Bool + @FocusState + private var audioFocused: Bool + @FocusState + private var playbackSpeedFocused: Bool + @FocusState + private var chaptersFocused: Bool + @FocusState + private var focusedSection: MediaSection? + @FocusState + private var focusedLayer: Layer? { + willSet { + updateFocusedLayer = newValue! - init(viewModel: VideoPlayerViewModel) { - self.viewModel = viewModel - self.chapterImages = viewModel.item.getChapterImage(maxWidth: 500) - } + if focusedSection == .titles { + lastFocusedLayer = newValue! + } + } + } - var body: some View { - ZStack(alignment: .bottom) { - LinearGradient(gradient: Gradient(colors: [.clear, .black.opacity(0.8), .black]), - startPoint: .top, - endPoint: .bottom) - .ignoresSafeArea() - .frame(height: 300) + init(viewModel: VideoPlayerViewModel) { + self.viewModel = viewModel + self.chapterImages = viewModel.item.getChapterImage(maxWidth: 500) + } - VStack { - Spacer() + var body: some View { + ZStack(alignment: .bottom) { + LinearGradient(gradient: Gradient(colors: [.clear, .black.opacity(0.8), .black]), + startPoint: .top, + endPoint: .bottom) + .ignoresSafeArea() + .frame(height: 300) - HStack { - // MARK: Subtitle Header + VStack { - Button { - updateFocusedLayer = .subtitles - focusedLayer = .subtitles - } label: { - if updateFocusedLayer == .subtitles { - HStack(spacing: 15) { - Image(systemName: "captions.bubble") - L10n.subtitles.text - } - .padding() - .background(Color.white) - .foregroundColor(.black) - } else { - HStack(spacing: 15) { - Image(systemName: "captions.bubble") - L10n.subtitles.text - } - .padding() - } - } - .buttonStyle(PlainButtonStyle()) - .background(Color.clear) - .focused($focusedLayer, equals: .subtitles) - .focused($subtitlesFocused) - .onChange(of: subtitlesFocused) { isFocused in - if isFocused { - focusedLayer = .subtitles - } - } + Spacer() - // MARK: Audio Header + HStack { - Button { - updateFocusedLayer = .audio - focusedLayer = .audio - } label: { - if updateFocusedLayer == .audio { - HStack(spacing: 15) { - Image(systemName: "speaker.wave.3") - L10n.audio.text - } - .padding() - .background(Color.white) - .foregroundColor(.black) - } else { - HStack(spacing: 15) { - Image(systemName: "speaker.wave.3") - L10n.audio.text - } - .padding() - } - } - .buttonStyle(PlainButtonStyle()) - .background(Color.clear) - .focused($focusedLayer, equals: .audio) - .focused($audioFocused) - .onChange(of: audioFocused) { isFocused in - if isFocused { - focusedLayer = .audio - } - } + // MARK: Subtitle Header - // MARK: Playback Speed Header + Button { + updateFocusedLayer = .subtitles + focusedLayer = .subtitles + } label: { + if updateFocusedLayer == .subtitles { + HStack(spacing: 15) { + Image(systemName: "captions.bubble") + L10n.subtitles.text + } + .padding() + .background(Color.white) + .foregroundColor(.black) + } else { + HStack(spacing: 15) { + Image(systemName: "captions.bubble") + L10n.subtitles.text + } + .padding() + } + } + .buttonStyle(PlainButtonStyle()) + .background(Color.clear) + .focused($focusedLayer, equals: .subtitles) + .focused($subtitlesFocused) + .onChange(of: subtitlesFocused) { isFocused in + if isFocused { + focusedLayer = .subtitles + } + } - Button { - updateFocusedLayer = .playbackSpeed - focusedLayer = .playbackSpeed - } label: { - if updateFocusedLayer == .playbackSpeed { - HStack(spacing: 15) { - Image(systemName: "speedometer") - L10n.playbackSpeed.text - } - .padding() - .background(Color.white) - .foregroundColor(.black) - } else { - HStack(spacing: 15) { - Image(systemName: "speedometer") - L10n.playbackSpeed.text - } - .padding() - } - } - .buttonStyle(PlainButtonStyle()) - .background(Color.clear) - .focused($focusedLayer, equals: .playbackSpeed) - .focused($playbackSpeedFocused) - .onChange(of: playbackSpeedFocused) { isFocused in - if isFocused { - focusedLayer = .playbackSpeed - } - } + // MARK: Audio Header - // MARK: Chapters Header + Button { + updateFocusedLayer = .audio + focusedLayer = .audio + } label: { + if updateFocusedLayer == .audio { + HStack(spacing: 15) { + Image(systemName: "speaker.wave.3") + L10n.audio.text + } + .padding() + .background(Color.white) + .foregroundColor(.black) + } else { + HStack(spacing: 15) { + Image(systemName: "speaker.wave.3") + L10n.audio.text + } + .padding() + } + } + .buttonStyle(PlainButtonStyle()) + .background(Color.clear) + .focused($focusedLayer, equals: .audio) + .focused($audioFocused) + .onChange(of: audioFocused) { isFocused in + if isFocused { + focusedLayer = .audio + } + } - if !viewModel.chapters.isEmpty { - Button { - updateFocusedLayer = .chapters - focusedLayer = .chapters - } label: { - if updateFocusedLayer == .chapters { - HStack(spacing: 15) { - Image(systemName: "list.dash") - L10n.chapters.text - } - .padding() - .background(Color.white) - .foregroundColor(.black) - } else { - HStack(spacing: 15) { - Image(systemName: "list.dash") - L10n.chapters.text - } - .padding() - } - } - .buttonStyle(PlainButtonStyle()) - .background(Color.clear) - .focused($focusedLayer, equals: .chapters) - .focused($chaptersFocused) - .onChange(of: chaptersFocused) { isFocused in - if isFocused { - focusedLayer = .chapters - } - } - } + // MARK: Playback Speed Header - Spacer() - } - .padding() - .focusSection() - .focused($focusedSection, equals: .titles) - .onChange(of: focusedSection) { _ in - if focusedSection == .titles { - if lastFocusedLayer == .subtitles { - subtitlesFocused = true - } else if lastFocusedLayer == .audio { - audioFocused = true - } else if lastFocusedLayer == .playbackSpeed { - playbackSpeedFocused = true - } - } - } + Button { + updateFocusedLayer = .playbackSpeed + focusedLayer = .playbackSpeed + } label: { + if updateFocusedLayer == .playbackSpeed { + HStack(spacing: 15) { + Image(systemName: "speedometer") + L10n.playbackSpeed.text + } + .padding() + .background(Color.white) + .foregroundColor(.black) + } else { + HStack(spacing: 15) { + Image(systemName: "speedometer") + L10n.playbackSpeed.text + } + .padding() + } + } + .buttonStyle(PlainButtonStyle()) + .background(Color.clear) + .focused($focusedLayer, equals: .playbackSpeed) + .focused($playbackSpeedFocused) + .onChange(of: playbackSpeedFocused) { isFocused in + if isFocused { + focusedLayer = .playbackSpeed + } + } - if updateFocusedLayer == .subtitles, lastFocusedLayer == .subtitles { - // MARK: Subtitles + // MARK: Chapters Header - subtitleMenuView - } else if updateFocusedLayer == .audio, lastFocusedLayer == .audio { - // MARK: Audio + if !viewModel.chapters.isEmpty { + Button { + updateFocusedLayer = .chapters + focusedLayer = .chapters + } label: { + if updateFocusedLayer == .chapters { + HStack(spacing: 15) { + Image(systemName: "list.dash") + L10n.chapters.text + } + .padding() + .background(Color.white) + .foregroundColor(.black) + } else { + HStack(spacing: 15) { + Image(systemName: "list.dash") + L10n.chapters.text + } + .padding() + } + } + .buttonStyle(PlainButtonStyle()) + .background(Color.clear) + .focused($focusedLayer, equals: .chapters) + .focused($chaptersFocused) + .onChange(of: chaptersFocused) { isFocused in + if isFocused { + focusedLayer = .chapters + } + } + } - audioMenuView - } else if updateFocusedLayer == .playbackSpeed, lastFocusedLayer == .playbackSpeed { - // MARK: Playback Speed + Spacer() + } + .padding() + .focusSection() + .focused($focusedSection, equals: .titles) + .onChange(of: focusedSection) { _ in + if focusedSection == .titles { + if lastFocusedLayer == .subtitles { + subtitlesFocused = true + } else if lastFocusedLayer == .audio { + audioFocused = true + } else if lastFocusedLayer == .playbackSpeed { + playbackSpeedFocused = true + } + } + } - playbackSpeedMenuView - } else if updateFocusedLayer == .chapters, lastFocusedLayer == .chapters { - // MARK: Chapters + if updateFocusedLayer == .subtitles && lastFocusedLayer == .subtitles { + // MARK: Subtitles - chaptersMenuView - } - } - } - } + subtitleMenuView + } else if updateFocusedLayer == .audio && lastFocusedLayer == .audio { + // MARK: Audio - @ViewBuilder - private var subtitleMenuView: some View { - ScrollView(.horizontal) { - HStack { - if viewModel.subtitleStreams.isEmpty { - Button {} label: { - L10n.none.text - } - } else { - ForEach(viewModel.subtitleStreams, id: \.self) { subtitleStream in - Button { - viewModel.selectedSubtitleStreamIndex = subtitleStream.index ?? -1 - } label: { - if subtitleStream.index == viewModel.selectedSubtitleStreamIndex { - Label(subtitleStream.displayTitle ?? L10n.noTitle, systemImage: "checkmark") - } else { - Text(subtitleStream.displayTitle ?? L10n.noTitle) - } - } - } - } - } - .padding(.vertical) - .focusSection() - .focused($focusedSection, equals: .items) - } - } + audioMenuView + } else if updateFocusedLayer == .playbackSpeed && lastFocusedLayer == .playbackSpeed { + // MARK: Playback Speed - @ViewBuilder - private var audioMenuView: some View { - ScrollView(.horizontal) { - HStack { - if viewModel.audioStreams.isEmpty { - Button {} label: { - Text("None") - } - } else { - ForEach(viewModel.audioStreams, id: \.self) { audioStream in - Button { - viewModel.selectedAudioStreamIndex = audioStream.index ?? -1 - } label: { - if audioStream.index == viewModel.selectedAudioStreamIndex { - Label(audioStream.displayTitle ?? L10n.noTitle, systemImage: "checkmark") - } else { - Text(audioStream.displayTitle ?? L10n.noTitle) - } - } - } - } - } - .padding(.vertical) - .focusSection() - .focused($focusedSection, equals: .items) - } - } + playbackSpeedMenuView + } else if updateFocusedLayer == .chapters && lastFocusedLayer == .chapters { + // MARK: Chapters - @ViewBuilder - private var playbackSpeedMenuView: some View { - ScrollView(.horizontal) { - HStack { - ForEach(PlaybackSpeed.allCases, id: \.self) { playbackSpeed in - Button { - viewModel.playbackSpeed = playbackSpeed - } label: { - if playbackSpeed == viewModel.playbackSpeed { - Label(playbackSpeed.displayTitle, systemImage: "checkmark") - } else { - Text(playbackSpeed.displayTitle) - } - } - } - } - .padding(.vertical) - .focusSection() - .focused($focusedSection, equals: .items) - } - } + chaptersMenuView + } + } + } + } - @ViewBuilder - private var chaptersMenuView: some View { - ScrollView(.horizontal, showsIndicators: false) { - ScrollViewReader { reader in - HStack { - ForEach(0 ..< viewModel.chapters.count, id: \.self) { chapterIndex in - VStack(alignment: .leading) { - Button { - viewModel.playerOverlayDelegate?.didSelectChapter(viewModel.chapters[chapterIndex]) - } label: { - ImageView(chapterImages[chapterIndex]) - .cornerRadius(10) - .frame(width: 350, height: 210) - } - .buttonStyle(CardButtonStyle()) + @ViewBuilder + private var subtitleMenuView: some View { + ScrollView(.horizontal) { + HStack { + if viewModel.subtitleStreams.isEmpty { + Button {} label: { + L10n.none.text + } + } else { + ForEach(viewModel.subtitleStreams, id: \.self) { subtitleStream in + Button { + viewModel.selectedSubtitleStreamIndex = subtitleStream.index ?? -1 + } label: { + if subtitleStream.index == viewModel.selectedSubtitleStreamIndex { + Label(subtitleStream.displayTitle ?? L10n.noTitle, systemImage: "checkmark") + } else { + Text(subtitleStream.displayTitle ?? L10n.noTitle) + } + } + } + } + } + .padding(.vertical) + .focusSection() + .focused($focusedSection, equals: .items) + } + } - VStack(alignment: .leading, spacing: 5) { - Text(viewModel.chapters[chapterIndex].name ?? L10n.noTitle) - .font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(.white) + @ViewBuilder + private var audioMenuView: some View { + ScrollView(.horizontal) { + HStack { + if viewModel.audioStreams.isEmpty { + Button {} label: { + Text("None") + } + } else { + ForEach(viewModel.audioStreams, id: \.self) { audioStream in + Button { + viewModel.selectedAudioStreamIndex = audioStream.index ?? -1 + } label: { + if audioStream.index == viewModel.selectedAudioStreamIndex { + Label(audioStream.displayTitle ?? L10n.noTitle, systemImage: "checkmark") + } else { + Text(audioStream.displayTitle ?? L10n.noTitle) + } + } + } + } + } + .padding(.vertical) + .focusSection() + .focused($focusedSection, equals: .items) + } + } - Text(viewModel.chapters[chapterIndex].timestampLabel) - .font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(Color(UIColor.systemBlue)) - .padding(.vertical, 2) - .padding(.horizontal, 4) - .background { - Color(UIColor.darkGray).opacity(0.2).cornerRadius(4) - } - } - } - .id(viewModel.chapters[chapterIndex]) - } - } - .padding(.top) - .onAppear { - reader.scrollTo(viewModel.currentChapter) - } - } - } - } + @ViewBuilder + private var playbackSpeedMenuView: some View { + ScrollView(.horizontal) { + HStack { + ForEach(PlaybackSpeed.allCases, id: \.self) { playbackSpeed in + Button { + viewModel.playbackSpeed = playbackSpeed + } label: { + if playbackSpeed == viewModel.playbackSpeed { + Label(playbackSpeed.displayTitle, systemImage: "checkmark") + } else { + Text(playbackSpeed.displayTitle) + } + } + } + } + .padding(.vertical) + .focusSection() + .focused($focusedSection, equals: .items) + } + } + + @ViewBuilder + private var chaptersMenuView: some View { + ScrollView(.horizontal, showsIndicators: false) { + ScrollViewReader { reader in + HStack { + ForEach(0 ..< viewModel.chapters.count, id: \.self) { chapterIndex in + VStack(alignment: .leading) { + Button { + viewModel.playerOverlayDelegate?.didSelectChapter(viewModel.chapters[chapterIndex]) + } label: { + ImageView(chapterImages[chapterIndex]) + .cornerRadius(10) + .frame(width: 350, height: 210) + } + .buttonStyle(CardButtonStyle()) + + VStack(alignment: .leading, spacing: 5) { + + Text(viewModel.chapters[chapterIndex].name ?? L10n.noTitle) + .font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.white) + + Text(viewModel.chapters[chapterIndex].timestampLabel) + .font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(Color(UIColor.systemBlue)) + .padding(.vertical, 2) + .padding(.horizontal, 4) + .background { + Color(UIColor.darkGray).opacity(0.2).cornerRadius(4) + } + } + } + .id(viewModel.chapters[chapterIndex]) + } + } + .padding(.top) + .onAppear { + reader.scrollTo(viewModel.currentChapter) + } + } + } + } } From 92e3c47214c08f717109eb637bbe48039915046c Mon Sep 17 00:00:00 2001 From: WWWesten Date: Mon, 2 May 2022 10:50:21 +0000 Subject: [PATCH 25/25] Translated using Weblate (Esperanto) Currently translated at 100.0% (192 of 192 strings) Translation: Swiftfin/Swiftfin Translate-URL: https://translate.jellyfin.org/projects/swiftfin/swiftfin/eo/ --- Translations/eo.lproj/Localizable.strings | Bin 18148 -> 18148 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Translations/eo.lproj/Localizable.strings b/Translations/eo.lproj/Localizable.strings index f017981d70af0a9e2d6140fce59666c81b400a75..a18d424ab2b5e9df8e556d2e3149415d0f6f88e8 100644 GIT binary patch delta 18 acmaFT%lM?1al<98$$JF2Hs8_WkO2Tx;0M~x^wFG4VLUIP!