Add related items, add logging statements.

This commit is contained in:
Aiden Vigue 2021-07-14 17:42:53 -04:00
parent c6a93868c7
commit f1dc7be31d
No known key found for this signature in database
GPG Key ID: B9A09843AB079D5B
15 changed files with 230 additions and 38 deletions

View File

@ -66,6 +66,8 @@
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 /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D73267BA8170004248C /* BackgroundManager.swift */; };
@ -398,6 +400,7 @@
628B95332670CAEA0091AF3B /* NukeUI in Frameworks */,
628B95242670CABD0091AF3B /* SwiftUI.framework in Frameworks */,
531ABF6C2671F5CC00C0FE20 /* WidgetKit.framework in Frameworks */,
53649AB5269D423A00A2D8B7 /* Puppy in Frameworks */,
536D3D7D267BD5F90004248C /* ActivityIndicator in Frameworks */,
628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */,
628B95352670CAEA0091AF3B /* JellyfinAPI in Frameworks */,
@ -780,6 +783,7 @@
628B95342670CAEA0091AF3B /* JellyfinAPI */,
628B95392670CE250091AF3B /* KeychainSwift */,
536D3D7C267BD5F90004248C /* ActivityIndicator */,
53649AB4269D423A00A2D8B7 /* Puppy */,
);
productName = WidgetExtensionExtension;
productReference = 628B95202670CABD0091AF3B /* WidgetExtension.appex */;
@ -1115,6 +1119,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
53649AB3269D3F5B00A2D8B7 /* LogManager.swift in Sources */,
62EC353126766848000E9F2D /* ServerEnvironment.swift in Sources */,
6267B3D42671024A00A7371D /* APIExtensions.swift in Sources */,
6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */,
@ -1163,6 +1168,7 @@
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 14.5;
@ -1346,6 +1352,7 @@
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@ -1406,6 +1413,7 @@
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@ -1601,6 +1609,11 @@
package = 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */;
productName = Puppy;
};
53649AB4269D423A00A2D8B7 /* Puppy */ = {
isa = XCSwiftPackageProductDependency;
package = 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */;
productName = Puppy;
};
536D3D7C267BD5F90004248C /* ActivityIndicator */ = {
isa = XCSwiftPackageProductDependency;
package = 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */;

View File

@ -180,6 +180,25 @@ struct EpisodeItemView: View {
}.padding(.leading, 16).padding(.trailing, 16)
}
}
if !(viewModel.similarItems).isEmpty {
Text("More Like This")
.font(.callout).fontWeight(.semibold).padding(.top, 3).padding(.leading, 16)
ScrollView(.horizontal, showsIndicators: false) {
VStack {
Spacer().frame(height: 8)
HStack {
Spacer().frame(width: 16)
ForEach(viewModel.similarItems, id: \.self) { similarItem in
NavigationLink(destination: LazyView { ItemView(item: similarItem) }) {
PortraitItemView(item: similarItem)
}
Spacer().frame(width: 10)
}
Spacer().frame(width: 16)
}
}
}.padding(.top, -5)
}
Spacer().frame(height: 3)
}
}
@ -357,6 +376,25 @@ struct EpisodeItemView: View {
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
}
}
if !(viewModel.similarItems).isEmpty {
Text("More Like This")
.font(.callout).fontWeight(.semibold).padding(.top, 3).padding(.leading, 16)
ScrollView(.horizontal, showsIndicators: false) {
VStack {
Spacer().frame(height: 8)
HStack {
Spacer().frame(width: 16)
ForEach(viewModel.similarItems, id: \.self) { similarItem in
NavigationLink(destination: LazyView { ItemView(item: similarItem) }) {
PortraitItemView(item: similarItem)
}
Spacer().frame(width: 10)
}
Spacer().frame(width: 16)
}
}
}.padding(.top, -5)
}
Spacer().frame(height: 195)
}.frame(maxHeight: .infinity)
}

View File

@ -6,6 +6,41 @@
*/
import SwiftUI
import MessageUI
// The notification we'll send when a shake gesture happens.
extension UIDevice {
static let deviceDidShakeNotification = Notification.Name(rawValue: "deviceDidShakeNotification")
}
// Override the default behavior of shake gestures to send our notification instead.
extension UIWindow {
open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
NotificationCenter.default.post(name: UIDevice.deviceDidShakeNotification, object: nil)
}
}
}
// A view modifier that detects shaking and calls a function of our choosing.
struct DeviceShakeViewModifier: ViewModifier {
let action: () -> Void
func body(content: Content) -> some View {
content
.onAppear()
.onReceive(NotificationCenter.default.publisher(for: UIDevice.deviceDidShakeNotification)) { _ in
action()
}
}
}
// A View extension to make the modifier easier to use.
extension View {
func onShake(perform action: @escaping () -> Void) -> some View {
self.modifier(DeviceShakeViewModifier(action: action))
}
}
extension UIDevice {
var hasNotch: Bool {
@ -138,6 +173,40 @@ extension View {
}
}
class EmailHelper: NSObject, MFMailComposeViewControllerDelegate {
public static let shared = EmailHelper()
private override init() {
//
}
func sendLogs(logURL: URL){
if !MFMailComposeViewController.canSendMail() {
// Utilities.showErrorBanner(title: "No mail account found", subtitle: "Please setup a mail account")
return //EXIT
}
let picker = MFMailComposeViewController()
let fileManager = FileManager()
let data = fileManager.contents(atPath: logURL.path)
picker.setSubject("SwiftFin Shake Report")
picker.setToRecipients(["Aiden Vigue <acvigue@me.com>"])
picker.addAttachmentData(data!, mimeType: "text/plain", fileName: logURL.lastPathComponent)
picker.mailComposeDelegate = self
EmailHelper.getRootViewController()?.present(picker, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
EmailHelper.getRootViewController()?.dismiss(animated: true, completion: nil)
}
static func getRootViewController() -> UIViewController? {
UIApplication.shared.windows.first?.rootViewController
}
}
@main
struct JellyfinPlayerApp: App {
let persistenceController = PersistenceController.shared
@ -149,6 +218,9 @@ struct JellyfinPlayerApp: App {
.withHostingWindow { window in
window?.rootViewController = PreferenceUIHostingController(wrappedView: SplashView().environment(\.managedObjectContext, persistenceController.container.viewContext))
}
.onShake {
EmailHelper.shared.sendLogs(logURL: LogManager.shared.logFileURL())
}
}
}
}

View File

@ -192,7 +192,26 @@ struct MovieItemView: View {
}.padding(.leading, 16).padding(.trailing, 16)
}
}
Spacer().frame(height: 3)
if !(viewModel.similarItems).isEmpty {
Text("More Like This")
.font(.callout).fontWeight(.semibold).padding(.top, 3).padding(.leading, 16)
ScrollView(.horizontal, showsIndicators: false) {
VStack {
Spacer().frame(height: 8)
HStack {
Spacer().frame(width: 16)
ForEach(viewModel.similarItems, id: \.self) { similarItem in
NavigationLink(destination: LazyView { ItemView(item: similarItem) }) {
PortraitItemView(item: similarItem)
}
Spacer().frame(width: 10)
}
Spacer().frame(width: 16)
}
}
}.padding(.top, -5)
}
Spacer().frame(height: 16)
}
}
} else {
@ -376,6 +395,25 @@ struct MovieItemView: View {
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
}
}
if !(viewModel.similarItems).isEmpty {
Text("More Like This")
.font(.callout).fontWeight(.semibold).padding(.top, 3).padding(.leading, 16)
ScrollView(.horizontal, showsIndicators: false) {
VStack {
Spacer().frame(height: 8)
HStack {
Spacer().frame(width: 16)
ForEach(viewModel.similarItems, id: \.self) { similarItem in
NavigationLink(destination: LazyView { ItemView(item: similarItem) }) {
PortraitItemView(item: similarItem)
}
Spacer().frame(width: 10)
}
Spacer().frame(width: 16)
}
}
}.padding(.top, -5)
}
Spacer().frame(height: 105)
}.frame(maxHeight: .infinity)
}

View File

@ -221,6 +221,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
// MARK: Cast methods
@IBAction func castButtonPressed(_ sender: Any) {
if selectedCastDevice == nil {
LogManager.shared.log.debug("Presenting Cast modal")
castDeviceVC = VideoPlayerCastDeviceSelectorView()
castDeviceVC?.delegate = self
@ -229,11 +230,11 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
// Present the view controller (in a popover).
self.present(castDeviceVC!, animated: true) {
print("popover visible, pause playback")
self.mediaPlayer.pause()
self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
}
} else {
LogManager.shared.log.info("Stopping casting session: button was pressed.")
castSessionManager.endSessionAndStopCasting(true)
selectedCastDevice = nil
self.castButton.isEnabled = true
@ -243,6 +244,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
}
func castPopoverDismissed() {
LogManager.shared.log.debug("Cast modal dismissed")
castDeviceVC?.dismiss(animated: true, completion: nil)
if playerDestination == .local {
self.mediaPlayer.play()
@ -251,7 +253,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
}
func castDeviceChanged() {
LogManager.shared.log.debug("Cast device changed")
if selectedCastDevice != nil {
LogManager.shared.log.debug("New device: \(selectedCastDevice?.friendlyName ?? "UNKNOWN")")
playerDestination = .remote
castSessionManager.add(self)
castSessionManager.startSession(with: selectedCastDevice!)
@ -613,7 +617,6 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
}
func startLocalPlaybackEngine(_ fetchCaptions: Bool) {
print("Local playback engine starting.")
mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl)
mediaPlayer.play()
sendPlayReport()
@ -621,10 +624,8 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
// 1 second = 10,000,000 ticks
var startTicks: Int64 = 0
if remotePositionTicks == 0 {
print("Using server-reported start time")
startTicks = manifest.userData?.playbackPositionTicks ?? 0
} else {
print("Using remote-reported start time")
startTicks = Int64(remotePositionTicks)
}
@ -632,7 +633,6 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
let videoPosition = Double(mediaPlayer.time.intValue / 1000)
let secondsScrubbedTo = startTicks / 10_000_000
let offset = secondsScrubbedTo - Int64(videoPosition)
print("Seeking to position: \(secondsScrubbedTo)")
if offset > 0 {
mediaPlayer.jumpForward(Int32(offset))
} else {
@ -641,8 +641,6 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
}
if fetchCaptions {
print("Fetching captions.")
// Pause and load captions into memory.
mediaPlayer.pause()
subtitleTrackArray.forEach { sub in
if sub.id != -1 && sub.delivery == .external {
@ -664,8 +662,6 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
mediaPlayer.pause()
mediaPlayer.play()
setupTracksForPreferredDefaults()
print("Local engine started.")
}
// MARK: VideoPlayerSettings Delegate
@ -820,7 +816,6 @@ extension PlayerViewController: GCKGenericChannelDelegate {
"receiverName": castSessionManager.currentCastSession!.device.friendlyName!,
"subtitleBurnIn": false
]
print(payload)
let jsonData = JSON(payload)
jellyfinCastChannel?.sendTextMessage(jsonData.rawString()!, error: nil)
@ -874,23 +869,24 @@ extension PlayerViewController: GCKSessionManagerListener {
}
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKCastSession) {
print("starting session")
self.jellyfinCastChannel = GCKGenericChannel(namespace: "urn:x-cast:com.connectsdk")
self.sessionDidStart(manager: sessionManager, didStart: session)
}
func sessionManager(_ sessionManager: GCKSessionManager, didResumeCastSession session: GCKCastSession) {
self.jellyfinCastChannel = GCKGenericChannel(namespace: "urn:x-cast:com.connectsdk")
print("resuming session")
self.sessionDidStart(manager: sessionManager, didStart: session)
}
func sessionManager(_ sessionManager: GCKSessionManager, didFailToStart session: GCKCastSession, withError error: Error) {
dump(error)
LogManager.shared.log.error((error as NSError).debugDescription)
}
func sessionManager(_ sessionManager: GCKSessionManager, didEnd session: GCKCastSession, withError error: Error?) {
print("didEnd")
if(error != nil) {
LogManager.shared.log.error((error! as NSError).debugDescription)
}
playerDestination = .local
videoContentView.isHidden = false
remoteTimeUpdateTimer?.invalidate()
@ -899,7 +895,6 @@ extension PlayerViewController: GCKSessionManagerListener {
}
func sessionManager(_ sessionManager: GCKSessionManager, didSuspend session: GCKCastSession, with reason: GCKConnectionSuspendReason) {
print("didSuspend")
playerDestination = .local
videoContentView.isHidden = false
remoteTimeUpdateTimer?.invalidate()
@ -914,27 +909,26 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
let currentState: VLCMediaPlayerState = mediaPlayer.state
switch currentState {
case .stopped :
LogManager.shared.log.debug("Player state changed: STOPPED")
break
case .ended :
LogManager.shared.log.debug("Player state changed: ENDED")
break
case .playing :
print("Video is playing")
LogManager.shared.log.debug("Player state changed: PLAYING")
sendProgressReport(eventName: "unpause")
delegate?.hideLoadingView(self)
paused = false
case .paused :
print("Video is paused)")
LogManager.shared.log.debug("Player state changed: PAUSED")
paused = true
case .opening :
print("Video is opening)")
LogManager.shared.log.debug("Player state changed: OPENING")
case .buffering :
print("Video is buffering)")
LogManager.shared.log.debug("Player state changed: BUFFERING")
delegate?.showLoadingView(self)
case .error :
print("Video has error)")
LogManager.shared.log.error("Video had error.")
sendStopReport()
case .esAdded:
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)

View File

@ -10,20 +10,39 @@
import Foundation
import Puppy
final class LogManager {
class LogManager {
static let shared = LogManager()
let log = Puppy()
init() {
let console = ConsoleLogger("me.vigue.jellyfin.ConsoleLogger")
let fileURL = URL(fileURLWithPath: "./app.log").absoluteURL
let file = try? FileLogger("me.vigue.jellyfin", fileURL: fileURL)
let fileURL = self.getDocumentsDirectory().appendingPathComponent("logs.txt")
let FM = FileManager()
_ = try? FM.removeItem(at: fileURL)
do {
let file = try FileLogger("me.vigue.jellyfin", 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)
if(file != nil) {
file!.format = LogFormatter();
log.add(file!, withLevel: .debug)
}
log.info("Logger initialized.")
}
func logFileURL() -> URL {
return 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]
}
}
@ -31,8 +50,7 @@ class LogFormatter: LogFormattable {
func formatMessage(_ level: LogLevel, message: String, tag: String, function: String,
file: String, line: UInt, swiftLogInfo: [String : String],
label: String, date: Date, threadID: UInt64) -> String {
let date = dateFormatter(date)
let file = shortFileName(file)
return "\(date) \(threadID) [\(level.emoji) \(level)] \(file)#L.\(line) \(function) \(message)"
let file = shortFileName(file).replacingOccurrences(of: ".swift", with: "")
return " [\(level.emoji) \(level)] \(file)#\(line):\(function) \(message)"
}
}

View File

@ -27,6 +27,7 @@ final class ServerEnvironment {
}
func create(with uri: String) -> AnyPublisher<Server, Error> {
LogManager.shared.log.debug("Initializing new Server object with raw URI: \"\(uri)\"")
var uri = uri
if !uri.contains("http") {
uri = "https://" + uri
@ -34,6 +35,7 @@ final class ServerEnvironment {
if uri.last == "/" {
uri = String(uri.dropLast())
}
LogManager.shared.log.debug("Normalized URI: \"\(uri)\", attempting to getPublicSystemInfo()")
JellyfinAPI.basePath = uri
return SystemAPI.getPublicSystemInfo()

View File

@ -65,6 +65,7 @@ final class SessionManager {
header.append("Device=\"\(deviceName)\", ")
if devID == nil {
LogManager.shared.log.info("Generating device ID...");
#if os(tvOS)
header.append("DeviceId=\"tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
deviceID = "tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
@ -73,6 +74,7 @@ final class SessionManager {
deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
#endif
} else {
LogManager.shared.log.info("Using stored device ID...");
header.append("DeviceId=\"\(devID!)\", ")
deviceID = devID!
}

View File

@ -33,12 +33,14 @@ final class HomeViewModel: ViewModel {
}
func refresh() {
LogManager.shared.log.debug("Refresh called.")
UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!)
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in
response.items!.forEach { item in
LogManager.shared.log.debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")")
if item.collectionType == "movies" || item.collectionType == "tvshows" {
self.libraries.append(item)
}
@ -51,6 +53,7 @@ final class HomeViewModel: ViewModel {
}, receiveValue: { response in
self.libraries.forEach { library in
if !(response.configuration?.latestItemsExcludes?.contains(library.id!))! {
LogManager.shared.log.debug("Adding library \(library.id!) (\(library.name ?? "nil")) to recently added list")
self.librariesShowRecentlyAddedIDs.append(library.id!)
}
}
@ -66,6 +69,7 @@ final class HomeViewModel: ViewModel {
.sink(receiveCompletion: { completion in
self.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in
LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) resume items")
self.resumeItems = response.items ?? []
})
.store(in: &cancellables)
@ -76,6 +80,7 @@ final class HomeViewModel: ViewModel {
.sink(receiveCompletion: { completion in
self.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { response in
LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) nextup items")
self.nextUpItems = response.items ?? []
})
.store(in: &cancellables)

View File

@ -25,6 +25,7 @@ final class LatestMediaViewModel: ViewModel {
}
func requestLatestMedia() {
LogManager.shared.log.debug("Requesting latest media for user id \(SessionManager.current.user.user_id ?? "NIL")")
UserLibraryAPI.getLatestMedia(userId: SessionManager.current.user.user_id!, parentId: libraryID,
fields: [
.primaryImageAspectRatio,
@ -40,6 +41,7 @@ final class LatestMediaViewModel: ViewModel {
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] response in
self?.items = response
LogManager.shared.log.debug("Retrieved \(String(self?.items.count ?? 0)) items")
})
.store(in: &cancellables)
}

View File

@ -22,6 +22,7 @@ final class SeasonItemViewModel: DetailItemViewModel {
}
func requestEpisodes() {
LogManager.shared.log.debug("Getting episodes in season \(self.item.id!) (\(self.item.name!)) of show \(self.item.seriesId!) (\(self.item.seriesName!))")
TvShowsAPI.getEpisodes(seriesId: item.seriesId ?? "", userId: SessionManager.current.user.user_id!,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
seasonId: item.id ?? "")
@ -30,6 +31,7 @@ final class SeasonItemViewModel: DetailItemViewModel {
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] response in
self?.episodes = response.items ?? []
LogManager.shared.log.debug("Retrieved \(String(self?.episodes.count ?? 0)) episodes")
})
.store(in: &cancellables)
}

View File

@ -24,6 +24,7 @@ final class SeriesItemViewModel: DetailItemViewModel {
}
func getNextUp() {
LogManager.shared.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))")
TvShowsAPI.getNextUp(userId: SessionManager.current.user.user_id!, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], seriesId: self.item.id!, enableUserData: true)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
@ -53,12 +54,14 @@ final class SeriesItemViewModel: DetailItemViewModel {
}
func requestSeasons() {
LogManager.shared.log.debug("Getting seasons of show \(self.item.id!) (\(self.item.name!))")
TvShowsAPI.getSeasons(seriesId: item.id ?? "", userId: SessionManager.current.user.user_id!, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], enableUserData: true)
.trackActivity(loading)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] response in
self?.seasons = response.items ?? []
LogManager.shared.log.debug("Retrieved \(String(self?.seasons.count ?? 0)) seasons")
})
.store(in: &cancellables)
}

View File

@ -43,10 +43,10 @@ final class SettingsViewModel: ObservableObject {
do {
self.bitrates = try JSONDecoder().decode([Bitrates].self, from: jsonData)
} catch {
print(error)
LogManager.shared.log.error("Error converting processed JSON into Swift compatible schema.")
}
} catch {
print(error)
LogManager.shared.log.error("Error processing JSON file `bitrates.json`")
}
self.langs = Locale.isoLanguageCodes.compactMap {

View File

@ -36,12 +36,12 @@ final class SplashViewModel: ViewModel {
}
@objc func didLogIn() {
print("didLogIn")
LogManager.shared.log.info("Received `didSignIn` from NSNotificationCenter.")
isLoggedIn = true
}
@objc func didLogOut() {
print("didLogOut")
LogManager.shared.log.info("Received `didSignOut` from NSNotificationCenter.")
isLoggedIn = false
}
}

View File

@ -40,9 +40,12 @@ class ViewModel: ObservableObject {
if let err = error as? ErrorResponse {
switch err {
case .error(401, _, _, _):
LogManager.shared.log.error("Request failed: User unauthorized, server returned a 401 error code.")
self.errorMessage = err.localizedDescription
SessionManager.current.logout()
case .error:
LogManager.shared.log.error("Request failed.")
LogManager.shared.log.error((err as NSError).debugDescription)
self.errorMessage = err.localizedDescription
}
}