From c14106381ab0b92c66f40f296d47aa23527a272f Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 17:13:58 -0700 Subject: [PATCH 01/18] fix logging outputs --- Shared/Singleton/LogManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Shared/Singleton/LogManager.swift b/Shared/Singleton/LogManager.swift index 8fbde19e..601ea4f1 100644 --- a/Shared/Singleton/LogManager.swift +++ b/Shared/Singleton/LogManager.swift @@ -15,13 +15,13 @@ class LogManager { let log = Puppy() init() { - let console = ConsoleLogger("me.vigue.jellyfin.ConsoleLogger") + 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("me.vigue.jellyfin", fileURL: fileURL) + let file = try FileLogger("com.swiftfin", fileURL: fileURL) file.format = LogFormatter() log.add(file, withLevel: .debug) } catch let err { From 622548e572db39f0b16ac9a64d1310ddb415c3ab Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 17:14:09 -0700 Subject: [PATCH 02/18] fix workspace --- Swiftfin.xcworkspace/contents.xcworkspacedata | 3 --- 1 file changed, 3 deletions(-) diff --git a/Swiftfin.xcworkspace/contents.xcworkspacedata b/Swiftfin.xcworkspace/contents.xcworkspacedata index a4438826..fe2f5a72 100644 --- a/Swiftfin.xcworkspace/contents.xcworkspacedata +++ b/Swiftfin.xcworkspace/contents.xcworkspacedata @@ -7,7 +7,4 @@ - - From c81306fd474ff5c2f172f2a844d426117d02e84f Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 17:20:16 -0700 Subject: [PATCH 03/18] remove bad cached image package --- Shared/Views/ImageView.swift | 8 +------- Shared/Views/ParallaxHeader.swift | 2 +- .../xcshareddata/swiftpm/Package.resolved | 9 --------- 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Shared/Views/ImageView.swift b/Shared/Views/ImageView.swift index 460e77a5..636d9900 100644 --- a/Shared/Views/ImageView.swift +++ b/Shared/Views/ImageView.swift @@ -7,7 +7,6 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ -import CachedAsyncImage import SwiftUI struct ImageView: View { @@ -41,7 +40,7 @@ struct ImageView: View { } var body: some View { - CachedAsyncImage(url: source, urlCache: .imageCache, transaction: Transaction(animation: .easeInOut)) { phase in + AsyncImage(url: source, transaction: Transaction(animation: .easeInOut)) { phase in switch phase { case .success(let image): image @@ -69,8 +68,3 @@ struct ImageView: View { } } } - -extension URLCache { - - static let imageCache = URLCache(memoryCapacity: 512*1000*1000, diskCapacity: 10*1000*1000*1000) -} diff --git a/Shared/Views/ParallaxHeader.swift b/Shared/Views/ParallaxHeader.swift index d5624f74..d16fccea 100644 --- a/Shared/Views/ParallaxHeader.swift +++ b/Shared/Views/ParallaxHeader.swift @@ -28,7 +28,7 @@ struct ParallaxHeaderScrollView 0 ? -proxy.frame(in: .global).minY : 0 header diff --git a/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved index cf80eed4..3ed0ea18 100644 --- a/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -100,15 +100,6 @@ "version": "1.4.2" } }, - { - "package": "CachedAsyncImage", - "repositoryURL": "https://github.com/lorenzofiamingo/SwiftUI-CachedAsyncImage", - "state": { - "branch": "main", - "revision": "eb489a699be1f6e6c1a19fecdd6bfdc556474fd6", - "version": null - } - }, { "package": "Introspect", "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect", From 59a19054c17ebd1d99ab972b5ec93130137ace26 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 17:20:37 -0700 Subject: [PATCH 04/18] remove bad cached image --- Swiftfin.xcodeproj/project.pbxproj | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index ed41be2f..f4edbe4e 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -408,8 +408,6 @@ E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; }; E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; }; E1E00A37278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; }; - E1E0F4D8278911680084F701 /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = E1E0F4D7278911680084F701 /* CachedAsyncImage */; }; - E1E0F4DA278911A30084F701 /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = E1E0F4D9278911A30084F701 /* CachedAsyncImage */; }; E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; }; E1E5D5372783A52C00692DFE /* CinematicEpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5362783A52C00692DFE /* CinematicEpisodeItemView.swift */; }; E1E5D5392783A56B00692DFE /* EpisodesRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */; }; @@ -759,7 +757,6 @@ 53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */, E1A9999B271A343C008E78C0 /* SwiftUICollection in Frameworks */, E13DD3CD27164CA7009D4DAF /* CoreStore in Frameworks */, - E1E0F4DA278911A30084F701 /* CachedAsyncImage in Frameworks */, E1AE8E7E2789136D00FBDDAA /* Nuke in Frameworks */, E178857D278037FD0094FBCF /* JellyfinAPI in Frameworks */, E12186DE2718F1C50010884C /* Defaults in Frameworks */, @@ -780,7 +777,6 @@ E1B6DCEA271A23880015B715 /* SwiftyJSON in Frameworks */, 53352571265EA0A0006CCA86 /* Introspect in Frameworks */, E13DD3C62716499E009D4DAF /* CoreStore in Frameworks */, - E1E0F4D8278911680084F701 /* CachedAsyncImage in Frameworks */, E1AE8E7C2789135A00FBDDAA /* Nuke in Frameworks */, 625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */, E1B6DCE8271A23780015B715 /* CombineExt in Frameworks */, @@ -1644,7 +1640,6 @@ E1218C9D271A2CD600EA0737 /* CombineExt */, E1A9999A271A343C008E78C0 /* SwiftUICollection */, E178857C278037FD0094FBCF /* JellyfinAPI */, - E1E0F4D9278911A30084F701 /* CachedAsyncImage */, E1AE8E7D2789136D00FBDDAA /* Nuke */, ); productName = "JellyfinPlayer tvOS"; @@ -1683,7 +1678,6 @@ E1A99998271A3429008E78C0 /* SwiftUICollection */, E10EAA44277BB646000269ED /* JellyfinAPI */, E10EAA4C277BB716000269ED /* Sliders */, - E1E0F4D7278911680084F701 /* CachedAsyncImage */, E1AE8E7B2789135A00FBDDAA /* Nuke */, ); productName = JellyfinPlayer; @@ -1776,7 +1770,6 @@ C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */, E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */, E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */, - E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */, E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; @@ -2973,14 +2966,6 @@ minimumVersion = 5.0.0; }; }; - E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/lorenzofiamingo/SwiftUI-CachedAsyncImage"; - requirement = { - branch = main; - kind = branch; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -3129,16 +3114,6 @@ package = E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */; productName = Nuke; }; - E1E0F4D7278911680084F701 /* CachedAsyncImage */ = { - isa = XCSwiftPackageProductDependency; - package = E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */; - productName = CachedAsyncImage; - }; - E1E0F4D9278911A30084F701 /* CachedAsyncImage */ = { - isa = XCSwiftPackageProductDependency; - package = E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */; - productName = CachedAsyncImage; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 5377CBE9263B596A003A4E83 /* Project object */; From f18b132b15be2fd0e3e5032baeeb7cb256c615c7 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 17:29:55 -0700 Subject: [PATCH 05/18] forgotten auto http populate for tvos --- Swiftfin tvOS/Views/ConnectToServerView.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Swiftfin tvOS/Views/ConnectToServerView.swift b/Swiftfin tvOS/Views/ConnectToServerView.swift index f47ff0b5..74aec005 100644 --- a/Swiftfin tvOS/Views/ConnectToServerView.swift +++ b/Swiftfin tvOS/Views/ConnectToServerView.swift @@ -6,13 +6,16 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ +import Defaults import SwiftUI import Stinsen struct ConnectToServerView: View { - @StateObject var viewModel = ConnectToServerViewModel() + @StateObject var viewModel: ConnectToServerViewModel @State var uri = "" + + @Default(.defaultHTTPScheme) var defaultHTTPScheme var body: some View { List { @@ -21,6 +24,12 @@ struct ConnectToServerView: View { .disableAutocorrection(true) .autocapitalization(.none) .keyboardType(.URL) + .onAppear { + if uri == "" { + uri = "\(defaultHTTPScheme.rawValue)://" + } + } + Button { viewModel.connectToServer(uri: uri) } label: { From d77f59d0487a579da7fbca4879184371e345c095 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 18:06:03 -0700 Subject: [PATCH 06/18] remove hls stream --- .../BaseItemDto+VideoPlayerViewModel.swift | 45 ++--- .../ServerStreamType.swift | 15 ++ .../VideoPlayerViewModel.swift | 6 +- .../NativePlayerViewController.swift | 164 ------------------ .../Views/VideoPlayer/VideoPlayerView.swift | 16 -- .../tvOSOverlay/tvOSVLCOverlay.swift | 2 +- Swiftfin.xcodeproj/project.pbxproj | 20 ++- .../VideoPlayer/VLCPlayerOverlayView.swift | 30 ++-- 8 files changed, 61 insertions(+), 237 deletions(-) create mode 100644 Shared/ViewModels/VideoPlayerViewModel/ServerStreamType.swift rename Shared/ViewModels/{ => VideoPlayerViewModel}/VideoPlayerViewModel.swift (99%) delete mode 100644 Swiftfin tvOS/Views/VideoPlayer/NativePlayerViewController.swift diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift index 4ee051d4..74c6e3cc 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift @@ -41,45 +41,24 @@ extension BaseItemDto { let defaultSubtitleStream = subtitleStreams.first(where: { $0.index! == mediaSource.defaultSubtitleStreamIndex ?? -1 }) - let videoStream = mediaSource.mediaStreams!.first(where: { $0.type! == MediaStreamType.video }) - - let audioCodecs = mediaSource.mediaStreams!.filter({ $0.type! == MediaStreamType.audio }).map({ $0.codec! }) - - // MARK: basic stream + // MARK: Stream var streamURL = URLComponents(string: SessionManager.main.currentLogin.server.currentURI)! - streamURL.path = "/Videos/\(self.id!)/stream" + + let streamType: ServerStreamType + + if let transcodeURL = mediaSource.transcodingUrl { + streamType = .transcode + streamURL.path = transcodeURL + } else { + streamType = .direct + streamURL.path = "/Videos/\(self.id!)/stream" + } streamURL.addQueryItem(name: "Static", value: "true") streamURL.addQueryItem(name: "MediaSourceId", value: self.id!) streamURL.addQueryItem(name: "Tag", value: self.etag) streamURL.addQueryItem(name: "MinSegments", value: "6") - // MARK: hls stream - var hlsURL = URLComponents(string: SessionManager.main.currentLogin.server.currentURI)! - hlsURL.path = "/videos/\(self.id!)/master.m3u8" - - hlsURL.addQueryItem(name: "DeviceId", value: UIDevice.vendorUUIDString) - hlsURL.addQueryItem(name: "MediaSourceId", value: self.id!) - hlsURL.addQueryItem(name: "VideoCodec", value: videoStream?.codec!) - hlsURL.addQueryItem(name: "AudioCodec", value: audioCodecs.joined(separator: ",")) - hlsURL.addQueryItem(name: "AudioStreamIndex", value: "\(defaultAudioStream!.index!)") - hlsURL.addQueryItem(name: "VideoBitrate", value: "\(videoStream!.bitRate!)") - hlsURL.addQueryItem(name: "AudioBitrate", value: "\(defaultAudioStream!.bitRate!)") - hlsURL.addQueryItem(name: "PlaySessionId", value: response.playSessionId!) - hlsURL.addQueryItem(name: "TranscodingMaxAudioChannels", value: "6") - hlsURL.addQueryItem(name: "RequireAvc", value: "false") - hlsURL.addQueryItem(name: "Tag", value: mediaSource.eTag!) - hlsURL.addQueryItem(name: "SegmentContainer", value: "ts") - hlsURL.addQueryItem(name: "MinSegments", value: "2") - hlsURL.addQueryItem(name: "BreakOnNonKeyFrames", value: "true") - hlsURL.addQueryItem(name: "TranscodeReasons", value: "VideoCodecNotSupported,AudioCodecNotSupported") - hlsURL.addQueryItem(name: "api_key", value: SessionManager.main.currentLogin.user.accessToken) - - if defaultSubtitleStream?.index != nil { - hlsURL.addQueryItem(name: "SubtitleMethod", value: "Encode") - hlsURL.addQueryItem(name: "SubtitleStreamIndex", value: "\(defaultSubtitleStream!.index!)") - } - // MARK: VidoPlayerViewModel Creation var subtitle: String? = nil @@ -110,7 +89,7 @@ extension BaseItemDto { title: modifiedSelfItem.name ?? "", subtitle: subtitle, streamURL: streamURL.url!, - hlsURL: hlsURL.url!, + streamType: streamType, response: response, audioStreams: audioStreams, subtitleStreams: subtitleStreams, diff --git a/Shared/ViewModels/VideoPlayerViewModel/ServerStreamType.swift b/Shared/ViewModels/VideoPlayerViewModel/ServerStreamType.swift new file mode 100644 index 00000000..8a850b56 --- /dev/null +++ b/Shared/ViewModels/VideoPlayerViewModel/ServerStreamType.swift @@ -0,0 +1,15 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Foundation + +enum ServerStreamType { + case direct + case transcode +} diff --git a/Shared/ViewModels/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift similarity index 99% rename from Shared/ViewModels/VideoPlayerViewModel.swift rename to Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift index aaa3311e..b36a7328 100644 --- a/Shared/ViewModels/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift @@ -85,12 +85,12 @@ final class VideoPlayerViewModel: ViewModel { let title: String let subtitle: String? let streamURL: URL - let hlsURL: URL let audioStreams: [MediaStream] let subtitleStreams: [MediaStream] let overlayType: OverlayType let jumpGesturesEnabled: Bool let resumeOffset: Bool + let streamType: ServerStreamType // MARK: Experimental let syncSubtitleStateWithAdjacent: Bool @@ -141,7 +141,7 @@ final class VideoPlayerViewModel: ViewModel { title: String, subtitle: String?, streamURL: URL, - hlsURL: URL, + streamType: ServerStreamType, response: PlaybackInfoResponse, audioStreams: [MediaStream], subtitleStreams: [MediaStream], @@ -157,7 +157,7 @@ final class VideoPlayerViewModel: ViewModel { self.title = title self.subtitle = subtitle self.streamURL = streamURL - self.hlsURL = hlsURL + self.streamType = streamType self.response = response self.audioStreams = audioStreams self.subtitleStreams = subtitleStreams diff --git a/Swiftfin tvOS/Views/VideoPlayer/NativePlayerViewController.swift b/Swiftfin tvOS/Views/VideoPlayer/NativePlayerViewController.swift deleted file mode 100644 index 8c28964c..00000000 --- a/Swiftfin tvOS/Views/VideoPlayer/NativePlayerViewController.swift +++ /dev/null @@ -1,164 +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 2021 Aiden Vigue & Jellyfin Contributors - */ - -import AVKit -import Combine -import JellyfinAPI -import UIKit - -class NativePlayerViewController: AVPlayerViewController { - - let viewModel: VideoPlayerViewModel - - var timeObserverToken: Any? - - var lastProgressTicks: Int64 = 0 - - private var cancellables = Set() - - init(viewModel: VideoPlayerViewModel) { - - self.viewModel = viewModel - - super.init(nibName: nil, bundle: nil) - - let player = AVPlayer(url: viewModel.hlsURL) - - player.appliesMediaSelectionCriteriaAutomatically = false - player.currentItem?.externalMetadata = createMetadata() - player.currentItem?.navigationMarkerGroups = createNavigationMarkerGroups() - -// let chevron = UIImage(systemName: "chevron.right.circle.fill")! -// let testAction = UIAction(title: "Next", image: chevron) { action in -// SessionAPI.sendSystemCommand(sessionId: viewModel.response.playSessionId!, command: .setSubtitleStreamIndex) -// .sink { completion in -// print(completion) -// } receiveValue: { _ in -// print("idk but we're here") -// } -// .store(in: &self.cancellables) -// } - -// self.transportBarCustomMenuItems = [testAction] - -// self.infoViewActions.append(testAction) - - let timeScale = CMTimeScale(NSEC_PER_SEC) - let time = CMTime(seconds: 5, preferredTimescale: timeScale) - - timeObserverToken = player.addPeriodicTimeObserver(forInterval: time, queue: .main) { [weak self] time in -// print("Timer timed: \(time)") - - if time.seconds != 0 { - self?.sendProgressReport(seconds: time.seconds) - } - } - - self.player = player - - self.allowsPictureInPicturePlayback = true - self.player?.allowsExternalPlayback = true - } - - private func createMetadata() -> [AVMetadataItem] { - let allMetadata: [AVMetadataIdentifier: Any] = [ - .commonIdentifierTitle: viewModel.title, - .iTunesMetadataTrackSubTitle: viewModel.subtitle ?? "", - .commonIdentifierArtwork: UIImage(data: try! Data(contentsOf: viewModel.item.getBackdropImage(maxWidth: 200)))?.pngData() as Any, - .commonIdentifierDescription: viewModel.item.overview ?? "", - .iTunesMetadataContentRating: viewModel.item.officialRating ?? "", - .quickTimeMetadataGenre: viewModel.item.genres?.first ?? "" - ] - - return allMetadata.compactMap { createMetadataItem(for:$0, value:$1) } - } - - private func createMetadataItem(for identifier: AVMetadataIdentifier, - value: Any) -> AVMetadataItem { - let item = AVMutableMetadataItem() - item.identifier = identifier - item.value = value as? NSCopying & NSObjectProtocol - // Specify "und" to indicate an undefined language. - item.extendedLanguageTag = "und" - return item.copy() as! AVMetadataItem - } - - private func createNavigationMarkerGroups() -> [AVNavigationMarkersGroup] { - guard let chapters = viewModel.item.chapters else { return [] } - - var metadataGroups: [AVTimedMetadataGroup] = [] - - // TODO: Determine range between chapters - chapters.forEach { chapterInfo in - var chapterMetadata: [AVMetadataItem] = [] - - let titleItem = createMetadataItem(for: .commonIdentifierTitle, value: chapterInfo.name ?? "No Name") - chapterMetadata.append(titleItem) - - let imageItem = createMetadataItem(for: .commonIdentifierArtwork, value: UIImage(data: try! Data(contentsOf: viewModel.item.getBackdropImage(maxWidth: 200)))?.pngData() as Any) - chapterMetadata.append(imageItem) - - let startTime = CMTimeMake(value: chapterInfo.startPositionTicks ?? 0, timescale: 10_000_000) - let endTime = CMTimeMake(value: (chapterInfo.startPositionTicks ?? 0) + 50_000_000, timescale: 10_000_000) - let timeRange = CMTimeRangeFromTimeToTime(start: startTime, end: endTime) - - metadataGroups.append(AVTimedMetadataGroup(items: chapterMetadata, timeRange: timeRange)) - } - - return [AVNavigationMarkersGroup(title: nil, timedNavigationMarkers: metadataGroups)] - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - stop() - removePeriodicTimeObserver() - } - - func removePeriodicTimeObserver() { - if let timeObserverToken = timeObserverToken { - player?.removeTimeObserver(timeObserverToken) - self.timeObserverToken = nil - } - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - player?.seek(to: CMTimeMake(value: viewModel.item.userData?.playbackPositionTicks ?? 0, timescale: 10_000_000), toleranceBefore: CMTimeMake(value: 5, timescale: 1), toleranceAfter: CMTimeMake(value: 5, timescale: 1), completionHandler: { _ in - self.play() - }) - } - - private func play() { - player?.play() - -// viewModel.sendPlayReport(startTimeTicks: viewModel.item.userData?.playbackPositionTicks ?? 0) - viewModel.sendPlayReport() - } - - private func sendProgressReport(seconds: Double) { -// viewModel.sendProgressReport(ticks: Int64(seconds) * 10_000_000) - viewModel.sendProgressReport() - } - - private func stop() { - self.player?.pause() - viewModel.sendStopReport() -// viewModel.sendStopReport(ticks: 10_000_000) - } -} diff --git a/Swiftfin tvOS/Views/VideoPlayer/VideoPlayerView.swift b/Swiftfin tvOS/Views/VideoPlayer/VideoPlayerView.swift index c1eb827e..099d0838 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/VideoPlayerView.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/VideoPlayerView.swift @@ -10,22 +10,6 @@ import UIKit import SwiftUI -struct NativePlayerView: UIViewControllerRepresentable { - - let viewModel: VideoPlayerViewModel - - typealias UIViewControllerType = NativePlayerViewController - - func makeUIViewController(context: Context) -> NativePlayerViewController { - - return NativePlayerViewController(viewModel: viewModel) - } - - func updateUIViewController(_ uiViewController: NativePlayerViewController, context: Context) { - - } -} - struct VLCPlayerView: UIViewControllerRepresentable { let viewModel: VideoPlayerViewModel diff --git a/Swiftfin tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift b/Swiftfin tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift index bce30b04..bcffe81b 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift @@ -140,7 +140,7 @@ struct tvOSVLCOverlay_Previews: PreviewProvider { title: "Glorious Purpose", subtitle: "Loki - S1E1", streamURL: URL(string: "www.apple.com")!, - hlsURL: URL(string: "www.apple.com")!, + streamType: .direct, response: PlaybackInfoResponse(), audioStreams: [MediaStream(displayTitle: "English", index: -1)], subtitleStreams: [MediaStream(displayTitle: "None", index: -1)], diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index f4edbe4e..e75290c9 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -274,6 +274,8 @@ E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; }; E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; }; 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 */; }; @@ -384,7 +386,6 @@ E1C812C3277A8E5D00918266 /* VLCPlayerOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812BB277A8E5D00918266 /* VLCPlayerOverlayView.swift */; }; E1C812C5277A90B200918266 /* URLComponentsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812C4277A90B200918266 /* URLComponentsExtensions.swift */; }; E1C812CA277AE40900918266 /* PlayerOverlayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812C6277AE40900918266 /* PlayerOverlayDelegate.swift */; }; - E1C812CB277AE40900918266 /* NativePlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812C7277AE40900918266 /* NativePlayerViewController.swift */; }; E1C812CC277AE40A00918266 /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812C8277AE40900918266 /* VideoPlayerView.swift */; }; E1C812CD277AE40A00918266 /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812C9277AE40900918266 /* VideoPlayerViewModel.swift */; }; E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812C9277AE40900918266 /* VideoPlayerViewModel.swift */; }; @@ -649,6 +650,7 @@ E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = ""; }; 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 = ""; }; E13D02842788B634000FCB04 /* Swiftfin.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Swiftfin.entitlements; sourceTree = ""; }; @@ -710,7 +712,6 @@ E1C812BB277A8E5D00918266 /* VLCPlayerOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VLCPlayerOverlayView.swift; sourceTree = ""; }; E1C812C4277A90B200918266 /* URLComponentsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponentsExtensions.swift; sourceTree = ""; }; E1C812C6277AE40900918266 /* PlayerOverlayDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerOverlayDelegate.swift; sourceTree = ""; }; - E1C812C7277AE40900918266 /* NativePlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativePlayerViewController.swift; sourceTree = ""; }; E1C812C8277AE40900918266 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; E1C812C9277AE40900918266 /* VideoPlayerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModel.swift; sourceTree = ""; }; E1C812D0277AE4E300918266 /* tvOSVideoPlayerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSVideoPlayerCoordinator.swift; sourceTree = ""; }; @@ -816,7 +817,6 @@ 5310694F2684E7EE00CFFDBA /* VideoPlayer */ = { isa = PBXGroup; children = ( - E1C812C7277AE40900918266 /* NativePlayerViewController.swift */, E1C812C6277AE40900918266 /* PlayerOverlayDelegate.swift */, E178859C2780F5300094FBCF /* tvOSSLider */, E17885A7278130690094FBCF /* tvOSOverlay */, @@ -850,7 +850,7 @@ E13DD3F82717E961009D4DAF /* UserListViewModel.swift */, E13DD3EB27178A54009D4DAF /* UserSignInViewModel.swift */, 09389CC626819B4500AE350E /* VideoPlayerModel.swift */, - E1C812C9277AE40900918266 /* VideoPlayerViewModel.swift */, + E126F73F278A655300A522BF /* VideoPlayerViewModel */, 625CB57B2678CE1000530A6E /* ViewModel.swift */, ); path = ViewModels; @@ -1362,6 +1362,15 @@ path = Views; sourceTree = ""; }; + E126F73F278A655300A522BF /* VideoPlayerViewModel */ = { + isa = PBXGroup; + children = ( + E126F740278A656C00A522BF /* ServerStreamType.swift */, + E1C812C9277AE40900918266 /* VideoPlayerViewModel.swift */, + ); + path = VideoPlayerViewModel; + sourceTree = ""; + }; E13DD3BB27163C3E009D4DAF /* App */ = { isa = PBXGroup; children = ( @@ -2041,7 +2050,6 @@ E11D224327378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */, E1D4BF8B2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */, E1E5D53B2783A80900692DFE /* CinematicItemViewTopRow.swift in Sources */, - E1C812CB277AE40900918266 /* NativePlayerViewController.swift in Sources */, E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */, C40CD926271F8D1E000FB198 /* MovieLibrariesViewModel.swift in Sources */, 62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */, @@ -2061,6 +2069,7 @@ E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */, 091B5A8E268315D400D78B61 /* UDPBroadCastConnection.swift in Sources */, E10EAA50277BBCC4000269ED /* CGSizeExtensions.swift in Sources */, + E126F742278A656C00A522BF /* ServerStreamType.swift in Sources */, E1E5D5422783B33900692DFE /* PortraitItemsRowView.swift in Sources */, E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */, 531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */, @@ -2322,6 +2331,7 @@ 5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */, E1D4BF8A2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */, E13DD3F92717E961009D4DAF /* UserListViewModel.swift in Sources */, + E126F741278A656C00A522BF /* ServerStreamType.swift in Sources */, 539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */, 5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */, 09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */, diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerOverlayView.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerOverlayView.swift index cf6240c7..87f5eefd 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerOverlayView.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerOverlayView.swift @@ -382,21 +382,21 @@ struct VLCPlayerOverlayView: View { struct VLCPlayerCompactOverlayView_Previews: PreviewProvider { static let videoPlayerViewModel = VideoPlayerViewModel(item: BaseItemDto(), - title: "Glorious Purpose", - subtitle: "Loki - S1E1", - streamURL: URL(string: "www.apple.com")!, - hlsURL: URL(string: "www.apple.com")!, - response: PlaybackInfoResponse(), - audioStreams: [MediaStream(displayTitle: "English", index: -1)], - subtitleStreams: [MediaStream(displayTitle: "None", index: -1)], - selectedAudioStreamIndex: -1, - selectedSubtitleStreamIndex: -1, - subtitlesEnabled: true, - autoplayEnabled: false, - overlayType: .compact, - shouldShowPlayPreviousItem: true, - shouldShowPlayNextItem: true, - shouldShowAutoPlay: true) + title: "Glorious Purpose", + subtitle: "Loki - S1E1", + streamURL: URL(string: "www.apple.com")!, + streamType: .direct, + response: PlaybackInfoResponse(), + audioStreams: [MediaStream(displayTitle: "English", index: -1)], + subtitleStreams: [MediaStream(displayTitle: "None", index: -1)], + selectedAudioStreamIndex: -1, + selectedSubtitleStreamIndex: -1, + subtitlesEnabled: true, + autoplayEnabled: false, + overlayType: .compact, + shouldShowPlayPreviousItem: true, + shouldShowPlayNextItem: true, + shouldShowAutoPlay: true) static var previews: some View { ZStack { From 5bfb8e240db99898f25b220e00def4cbb23a3d9d Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 23:40:40 -0700 Subject: [PATCH 07/18] get latest items and tvos cinematic home view --- Shared/Generated/Strings.swift | 6 +- Shared/ViewModels/HomeViewModel.swift | 41 ++++-- .../CinematicBackgroundView.swift | 55 ++++++++ .../CinematicNextUpCardView.swift | 62 +++++++++ .../CinematicResumeCardView.swift | 61 +++++++++ .../HomeCinematicView/HomeCinematicView.swift | 123 ++++++++++++++++++ .../UICinematicBackgroundView.swift | 71 ++++++++++ Swiftfin tvOS/Views/HomeView.swift | 31 ++++- Swiftfin tvOS/Views/LatestMediaView.swift | 2 +- Swiftfin.xcodeproj/project.pbxproj | 28 ++++ Translations/en.lproj/Localizable.strings | Bin 4714 -> 4724 bytes 11 files changed, 463 insertions(+), 17 deletions(-) create mode 100644 Swiftfin tvOS/Components/HomeCinematicView/CinematicBackgroundView.swift create mode 100644 Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift create mode 100644 Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift create mode 100644 Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift create mode 100644 Swiftfin tvOS/Components/HomeCinematicView/UICinematicBackgroundView.swift diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index 6e6ea374..3705fd7b 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -68,9 +68,9 @@ internal enum L10n { internal static let genres = L10n.tr("Localizable", "genres") /// Home internal static let home = L10n.tr("Localizable", "home") - /// Latest %@ - internal static func latestWithString(_ p1: Any) -> String { - return L10n.tr("Localizable", "latestWithString", String(describing: p1)) + /// Latest in %@ + internal static func latestInWithString(_ p1: Any) -> String { + return L10n.tr("Localizable", "latestInWithString", String(describing: p1)) } /// Library internal static let library = L10n.tr("Localizable", "library") diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index e00f07c5..8207fedd 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -14,10 +14,11 @@ import JellyfinAPI final class HomeViewModel: ViewModel { - @Published var librariesShowRecentlyAddedIDs: [String] = [] - @Published var libraries: [BaseItemDto] = [] + @Published var latestAddedItems: [BaseItemDto] = [] @Published var resumeItems: [BaseItemDto] = [] @Published var nextUpItems: [BaseItemDto] = [] + @Published var librariesShowRecentlyAddedIDs: [String] = [] + @Published var libraries: [BaseItemDto] = [] // temp var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded]) @@ -59,6 +60,7 @@ final class HomeViewModel: ViewModel { LogManager.shared.log.debug("Refresh called.") refreshLibrariesLatest() + refreshLatestAddedItems() refreshResumeItems() refreshNextUpItems() } @@ -111,13 +113,34 @@ final class HomeViewModel: ViewModel { .store(in: &cancellables) } + // MARK: Latest Added Items + private func refreshLatestAddedItems() { + UserLibraryAPI.getLatestMedia(userId: SessionManager.main.currentLogin.user.id, + fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters], + enableImageTypes: [.primary, .backdrop, .thumb], + enableUserData: true, + limit: 8) + .sink { completion in + switch completion { + case .finished: () + case .failure: + self.nextUpItems = [] + self.handleAPIRequestError(completion: completion) + } + } receiveValue: { items in + LogManager.shared.log.debug("Retrieved \(String(items.count)) resume items") + + self.latestAddedItems = items + } + .store(in: &cancellables) + } + // MARK: Resume Items private func refreshResumeItems() { - ItemsAPI.getResumeItems(userId: SessionManager.main.currentLogin.user.id, limit: 12, + ItemsAPI.getResumeItems(userId: SessionManager.main.currentLogin.user.id, + limit: 6, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters], - mediaTypes: ["Video"], - imageTypeLimit: 1, - enableImageTypes: [.primary, .backdrop, .thumb]) + enableUserData: true) .trackActivity(loading) .sink(receiveCompletion: { completion in switch completion { @@ -136,8 +159,10 @@ final class HomeViewModel: ViewModel { // MARK: Next Up Items private func refreshNextUpItems() { - TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id, limit: 12, - fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters]) + TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id, + limit: 6, + fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters], + enableUserData: true) .trackActivity(loading) .sink(receiveCompletion: { completion in switch completion { diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicBackgroundView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicBackgroundView.swift new file mode 100644 index 00000000..91cb3c5e --- /dev/null +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicBackgroundView.swift @@ -0,0 +1,55 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import JellyfinAPI +import Nuke +import SwiftUI +import UIKit + +class DynamicCinematicBackgroundViewModel: ObservableObject { + + @Published var currentItem: BaseItemDto? + @Published var currentImageView: UIImageView? + + func select(item: BaseItemDto) { + + guard item.id != currentItem?.id else { return } + + currentItem = item + + let itemImageView = UIImageView() + + let backdropImage: URL + + if item.itemType == .episode { + backdropImage = item.getSeriesBackdropImage(maxWidth: 1920) + } else { + backdropImage = item.getBackdropImage(maxWidth: 1920) + } + + let options = ImageLoadingOptions(transition: .fadeIn(duration: 0.2)) + + Nuke.loadImage(with: backdropImage, options: options, into: itemImageView, completion: { _ in }) + + currentImageView = itemImageView + } +} + +struct CinematicBackgroundView: UIViewRepresentable { + + @ObservedObject var viewModel: DynamicCinematicBackgroundViewModel + + func updateUIView(_ uiView: UICinematicBackgroundView, context: Context) { + uiView.update(imageView: viewModel.currentImageView ?? UIImageView()) + } + + func makeUIView(context: Context) -> UICinematicBackgroundView { + return UICinematicBackgroundView(initialImageView: viewModel.currentImageView ?? UIImageView()) + } +} diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift new file mode 100644 index 00000000..414f8342 --- /dev/null +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift @@ -0,0 +1,62 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import JellyfinAPI +import SwiftUI + +struct CinematicNextUpCardView: View { + + @EnvironmentObject var homeRouter: HomeCoordinator.Router + let item: BaseItemDto + let showOverlay: Bool + + var body: some View { + VStack(alignment: .leading) { + Button { + homeRouter.route(to: \.modalItem, item) + } label: { + ZStack(alignment: .bottomLeading) { + + if item.itemType == .episode { + ImageView(src: item.getSeriesBackdropImage(maxWidth: 350)) + .frame(width: 350, height: 210) + } else { + ImageView(src: item.getBackdropImage(maxWidth: 350)) + .frame(width: 350, height: 210) + } + + LinearGradient(colors: [.clear, .black], + startPoint: .top, + endPoint: .bottom) + .frame(height: 105) + .ignoresSafeArea() + + if showOverlay { + VStack(alignment: .leading, spacing: 0) { + Text("Next") + .font(.subheadline) + .padding(.vertical, 5) + .padding(.leading, 10) + .foregroundColor(.white) + + HStack { + Color.clear + .frame(width: 1, height: 7) + } + } + } + } + .frame(width: 350, height: 210) + } + .buttonStyle(CardButtonStyle()) + .padding(.top) + } + .padding(.vertical) + } +} diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift new file mode 100644 index 00000000..9529976c --- /dev/null +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift @@ -0,0 +1,61 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import JellyfinAPI +import SwiftUI + +struct CinematicResumeCardView: View { + + @EnvironmentObject var homeRouter: HomeCoordinator.Router + let item: BaseItemDto + + var body: some View { + VStack(alignment: .leading) { + Button { + homeRouter.route(to: \.modalItem, item) + } label: { + ZStack(alignment: .bottom) { + + if item.itemType == .episode { + ImageView(src: item.getSeriesBackdropImage(maxWidth: 350)) + .frame(width: 350, height: 210) + } else { + ImageView(src: item.getBackdropImage(maxWidth: 350)) + .frame(width: 350, height: 210) + } + + LinearGradient(colors: [.clear, .black], + startPoint: .top, + endPoint: .bottom) + .frame(height: 105) + .ignoresSafeArea() + + VStack(alignment: .leading, spacing: 0) { + Text(item.getItemProgressString() ?? "") + .font(.subheadline) + .padding(.vertical, 5) + .padding(.leading, 10) + .foregroundColor(.white) + + HStack { + Color(UIColor.systemPurple) + .frame(width: 350 * (item.userData?.playedPercentage ?? 0) / 100, height: 7) + + Spacer(minLength: 0) + } + } + } + .frame(width: 350, height: 210) + } + .buttonStyle(CardButtonStyle()) + .padding(.top) + } + .padding(.vertical) + } +} diff --git a/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift b/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift new file mode 100644 index 00000000..37edaa8d --- /dev/null +++ b/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift @@ -0,0 +1,123 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import SwiftUI +import UIKit +import JellyfinAPI + +struct HomeCinematicViewItem: Hashable { + + enum TopRowType { + case resume + case nextUp + case plain + } + + let item: BaseItemDto + let type: TopRowType + + func hash(into hasher: inout Hasher) { + hasher.combine(item) + hasher.combine(type) + } +} + +struct HomeCinematicView: View { + + @FocusState var selectedItem: BaseItemDto? + @State private var updatedSelectedItem: BaseItemDto? + @State private var initiallyAppeared = false + private let forcedItemSubtitle: String? + private let items: [HomeCinematicViewItem] + private let backgroundViewModel = DynamicCinematicBackgroundViewModel() + + init(items: [HomeCinematicViewItem], forcedItemSubtitle: String? = nil) { + self.items = items + self.forcedItemSubtitle = forcedItemSubtitle + } + + var body: some View { + + ZStack(alignment: .bottom) { + + CinematicBackgroundView(viewModel: backgroundViewModel) + .frame(height: UIScreen.main.bounds.height - 10) + + LinearGradient(stops: [.init(color: .clear, location: 0.5), + .init(color: .black.opacity(0.6), location: 0.7), + .init(color: .black, location: 1)], + startPoint: .top, + endPoint: .bottom) + .ignoresSafeArea() + + VStack(alignment: .leading, spacing: 0) { + + VStack(alignment: .leading, spacing: 0) { + + if let forcedItemSubtitle = forcedItemSubtitle { + Text(forcedItemSubtitle) + .font(.callout) + .fontWeight(.medium) + .foregroundColor(Color.secondary) + } else { + if updatedSelectedItem?.itemType == .episode { + Text(updatedSelectedItem?.getEpisodeLocator() ?? "") + .font(.callout) + .fontWeight(.medium) + .foregroundColor(Color.secondary) + } else { + Text("") + } + } + + Text("\(updatedSelectedItem?.seriesName ?? updatedSelectedItem?.name ?? "")") + .font(.title) + .fontWeight(.semibold) + .foregroundColor(.primary) + .lineLimit(1) + .fixedSize(horizontal: false, vertical: true) + } + .padding(.horizontal, 50) + + ScrollView(.horizontal, showsIndicators: false) { + HStack { + ForEach(items, id: \.self) { item in + switch item.type { + case .nextUp: + CinematicNextUpCardView(item: item.item, showOverlay: true) + .focused($selectedItem, equals: item.item) + case .resume: + CinematicResumeCardView(item: item.item) + .focused($selectedItem, equals: item.item) + case .plain: + CinematicNextUpCardView(item: item.item, showOverlay: false) + .focused($selectedItem, equals: item.item) + } + } + } + .padding(.horizontal, 50) + .padding(.bottom) + } + .focusSection() + } + } + .onChange(of: selectedItem) { newValue in + if let newItem = newValue { + backgroundViewModel.select(item: newItem) + updatedSelectedItem = newItem + } + } + .onAppear { + guard !initiallyAppeared else { return } + selectedItem = items.first?.item + updatedSelectedItem = items.first?.item + initiallyAppeared = true + } + } +} diff --git a/Swiftfin tvOS/Components/HomeCinematicView/UICinematicBackgroundView.swift b/Swiftfin tvOS/Components/HomeCinematicView/UICinematicBackgroundView.swift new file mode 100644 index 00000000..86c77b11 --- /dev/null +++ b/Swiftfin tvOS/Components/HomeCinematicView/UICinematicBackgroundView.swift @@ -0,0 +1,71 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import SwiftUI +import UIKit + +class UICinematicBackgroundView: UIView { + + private var currentImageView: UIView? + + private var selectDelayTimer: Timer? + + init(initialImageView: UIImageView) { + super.init(frame: .zero) + + initialImageView.translatesAutoresizingMaskIntoConstraints = false + initialImageView.alpha = 0 + + addSubview(initialImageView) + NSLayoutConstraint.activate([ + initialImageView.topAnchor.constraint(equalTo: topAnchor), + initialImageView.bottomAnchor.constraint(equalTo: bottomAnchor), + initialImageView.leftAnchor.constraint(equalTo: leftAnchor), + initialImageView.rightAnchor.constraint(equalTo: rightAnchor) + ]) + + self.currentImageView = initialImageView + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(imageView: UIImageView) { + + selectDelayTimer?.invalidate() + + selectDelayTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(delayTimerTimed), userInfo: imageView, repeats: false) + + } + + @objc private func delayTimerTimed(timer: Timer) { + let newImageView = timer.userInfo as! UIImageView + + newImageView.translatesAutoresizingMaskIntoConstraints = false + newImageView.alpha = 0 + + addSubview(newImageView) + NSLayoutConstraint.activate([ + newImageView.topAnchor.constraint(equalTo: topAnchor), + newImageView.bottomAnchor.constraint(equalTo: bottomAnchor), + newImageView.leftAnchor.constraint(equalTo: leftAnchor), + newImageView.rightAnchor.constraint(equalTo: rightAnchor) + ]) + + UIView.animate(withDuration: 0.2) { + newImageView.alpha = 1 + self.currentImageView?.alpha = 0 + } completion: { _ in + self.currentImageView?.removeFromSuperview() + self.currentImageView = newImageView + } + + } +} diff --git a/Swiftfin tvOS/Views/HomeView.swift b/Swiftfin tvOS/Views/HomeView.swift index 0c67b482..78fcbfa5 100644 --- a/Swiftfin tvOS/Views/HomeView.swift +++ b/Swiftfin tvOS/Views/HomeView.swift @@ -7,13 +7,16 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ +import Defaults import Foundation import SwiftUI +import JellyfinAPI struct HomeView: View { @EnvironmentObject var homeRouter: HomeCoordinator.Router @ObservedObject var viewModel = HomeViewModel() + @Default(.showPosterLabels) var showPosterLabels @State var showingSettings = false @@ -24,16 +27,33 @@ struct HomeView: View { } else { ScrollView { LazyVStack(alignment: .leading) { - if !viewModel.resumeItems.isEmpty { - ContinueWatchingView(items: viewModel.resumeItems) - } - if !viewModel.nextUpItems.isEmpty { - NextUpView(items: viewModel.nextUpItems) + if viewModel.resumeItems.isEmpty { + HomeCinematicView(items: viewModel.latestAddedItems.map({ .init(item: $0, type: .plain) }), + forcedItemSubtitle: "Recently Added") + + if !viewModel.nextUpItems.isEmpty { + NextUpView(items: viewModel.nextUpItems) + .focusSection() + } + } else { + HomeCinematicView(items: viewModel.resumeItems.map({ .init(item: $0, type: .resume) })) + + if !viewModel.nextUpItems.isEmpty { + NextUpView(items: viewModel.nextUpItems) + .focusSection() + } + + PortraitItemsRowView(rowTitle: "Recently Added", + items: viewModel.latestAddedItems, + showItemTitles: showPosterLabels) { item in + homeRouter.route(to: \.item, item) + } } ForEach(viewModel.libraries, id: \.self) { library in LatestMediaView(viewModel: LatestMediaViewModel(library: library)) + .focusSection() } Spacer(minLength: 100) @@ -52,6 +72,7 @@ struct HomeView: View { .focusSection() } } + .edgesIgnoringSafeArea(.top) .edgesIgnoringSafeArea(.horizontal) } } diff --git a/Swiftfin tvOS/Views/LatestMediaView.swift b/Swiftfin tvOS/Views/LatestMediaView.swift index 68fbe395..cc5e96a7 100644 --- a/Swiftfin tvOS/Views/LatestMediaView.swift +++ b/Swiftfin tvOS/Views/LatestMediaView.swift @@ -18,7 +18,7 @@ struct LatestMediaView: View { var body: some View { VStack(alignment: .leading) { - L10n.latestWithString(viewModel.library.name ?? "").text + L10n.latestInWithString(viewModel.library.name ?? "").text .font(.title3) .padding(.horizontal, 50) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index e75290c9..9c5b8dd2 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -246,6 +246,11 @@ C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; }; C4E52305272CE68800654268 /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */; }; E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; }; + E103A6A0278A7E4500820EC7 /* UICinematicBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A69F278A7E4500820EC7 /* UICinematicBackgroundView.swift */; }; + E103A6A3278A7EC400820EC7 /* CinematicBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A6A2278A7EC400820EC7 /* CinematicBackgroundView.swift */; }; + E103A6A5278A82E500820EC7 /* HomeCinematicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A6A4278A82E500820EC7 /* HomeCinematicView.swift */; }; + E103A6A7278AB6D700820EC7 /* CinematicResumeCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A6A6278AB6D700820EC7 /* CinematicResumeCardView.swift */; }; + E103A6A9278AB6FF00820EC7 /* CinematicNextUpCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A6A8278AB6FF00820EC7 /* CinematicNextUpCardView.swift */; }; E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */; }; E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */; }; E107BB972788104100354E07 /* CinematicCollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB952788104100354E07 /* CinematicCollectionItemView.swift */; }; @@ -638,6 +643,11 @@ C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = ""; }; C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelItemElement.swift; sourceTree = ""; }; E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayButtonRowView.swift; sourceTree = ""; }; + E103A69F278A7E4500820EC7 /* UICinematicBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICinematicBackgroundView.swift; sourceTree = ""; }; + E103A6A2278A7EC400820EC7 /* CinematicBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicBackgroundView.swift; sourceTree = ""; }; + E103A6A4278A82E500820EC7 /* HomeCinematicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCinematicView.swift; sourceTree = ""; }; + E103A6A6278AB6D700820EC7 /* CinematicResumeCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicResumeCardView.swift; sourceTree = ""; }; + E103A6A8278AB6FF00820EC7 /* CinematicNextUpCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicNextUpCardView.swift; sourceTree = ""; }; E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemViewModel.swift; sourceTree = ""; }; E107BB952788104100354E07 /* CinematicCollectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicCollectionItemView.swift; sourceTree = ""; }; E10D87D92784E4F100BD264C /* ItemViewDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewDetailsView.swift; sourceTree = ""; }; @@ -977,6 +987,7 @@ E1E5D5412783B33900692DFE /* PortraitItemsRowView.swift */, 536D3D87267C17350004248C /* PublicUserButton.swift */, E17885A3278105170094FBCF /* SFSymbolButton.swift */, + E103A6A1278A7EB500820EC7 /* HomeCinematicView */, ); path = Components; sourceTree = ""; @@ -1312,6 +1323,18 @@ path = Pods; sourceTree = ""; }; + E103A6A1278A7EB500820EC7 /* HomeCinematicView */ = { + isa = PBXGroup; + children = ( + E103A6A2278A7EC400820EC7 /* CinematicBackgroundView.swift */, + E103A6A8278AB6FF00820EC7 /* CinematicNextUpCardView.swift */, + E103A6A6278AB6D700820EC7 /* CinematicResumeCardView.swift */, + E103A6A4278A82E500820EC7 /* HomeCinematicView.swift */, + E103A69F278A7E4500820EC7 /* UICinematicBackgroundView.swift */, + ); + path = HomeCinematicView; + sourceTree = ""; + }; E107BB9127880A4000354E07 /* ItemViewModel */ = { isa = PBXGroup; children = ( @@ -2039,6 +2062,7 @@ E193D4DC27193CCA00900D82 /* PillStackable.swift in Sources */, E193D53327193F7D00900D82 /* FilterCoordinator.swift in Sources */, 6267B3DC2671139500A7371D /* ImageExtensions.swift in Sources */, + E103A6A9278AB6FF00820EC7 /* CinematicNextUpCardView.swift in Sources */, E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */, C40CD929271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */, C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */, @@ -2064,10 +2088,12 @@ 53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */, 53CD2A42268A4B38002ABD4E /* MovieItemView.swift in Sources */, E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */, + E103A6A3278A7EC400820EC7 /* CinematicBackgroundView.swift in Sources */, E178859E2780F53B0094FBCF /* SliderView.swift in Sources */, 536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */, E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */, 091B5A8E268315D400D78B61 /* UDPBroadCastConnection.swift in Sources */, + E103A6A5278A82E500820EC7 /* HomeCinematicView.swift in Sources */, E10EAA50277BBCC4000269ED /* CGSizeExtensions.swift in Sources */, E126F742278A656C00A522BF /* ServerStreamType.swift in Sources */, E1E5D5422783B33900692DFE /* PortraitItemsRowView.swift in Sources */, @@ -2098,6 +2124,7 @@ C4BE0767271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */, E193D53727193F8700900D82 /* LibraryListCoordinator.swift in Sources */, E10D87DF278510E400BD264C /* PosterSize.swift in Sources */, + E103A6A0278A7E4500820EC7 /* UICinematicBackgroundView.swift in Sources */, E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */, E17885A02780F55C0094FBCF /* tvOSVLCOverlay.swift in Sources */, E193D54D2719426600900D82 /* LibraryFilterView.swift in Sources */, @@ -2122,6 +2149,7 @@ 62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */, 536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */, E1AA33232782648000F6439C /* OverlaySliderColor.swift in Sources */, + E103A6A7278AB6D700820EC7 /* CinematicResumeCardView.swift in Sources */, 62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */, 5398514726B64E4100101B49 /* SearchBarView.swift in Sources */, E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */, diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index a22e6cfc0557333da07933dee274ee9d36e13f32..d5a9e33877e34f865afc95d4ed87707eee7c9cc0 100644 GIT binary patch delta 24 gcmaE*@ Date: Sat, 8 Jan 2022 23:44:15 -0700 Subject: [PATCH 08/18] ios recently added --- Swiftfin/Views/HomeView.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Swiftfin/Views/HomeView.swift b/Swiftfin/Views/HomeView.swift index 16274325..e81881f0 100644 --- a/Swiftfin/Views/HomeView.swift +++ b/Swiftfin/Views/HomeView.swift @@ -53,6 +53,7 @@ struct HomeView: View { if !viewModel.resumeItems.isEmpty { ContinueWatchingView(viewModel: viewModel) } + if !viewModel.nextUpItems.isEmpty { PortraitImageHStackView(items: viewModel.nextUpItems, horizontalAlignment: .leading) { @@ -63,14 +64,24 @@ struct HomeView: View { } selectedAction: { item in homeRouter.route(to: \.item, item) } - + } + + if !viewModel.latestAddedItems.isEmpty { + PortraitImageHStackView(items: viewModel.latestAddedItems) { + Text("Recently Added") + .font(.title2) + .fontWeight(.bold) + .padding() + } selectedAction: { item in + homeRouter.route(to: \.item, item) + } } ForEach(viewModel.libraries, id: \.self) { library in LatestMediaView(viewModel: LatestMediaViewModel(library: library)) { HStack { - Text(L10n.latestWithString(library.name ?? "")) + Text(L10n.latestInWithString(library.name ?? "")) .font(.title2) .fontWeight(.bold) From 875eafecd17b5974f75c8de1baf857d46a2ffa1b Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 23:52:25 -0700 Subject: [PATCH 09/18] switch back string --- Shared/Generated/Strings.swift | 6 +++--- Swiftfin tvOS/Views/LatestMediaView.swift | 2 +- Swiftfin/Views/HomeView.swift | 2 +- Translations/en.lproj/Localizable.strings | Bin 4724 -> 4714 bytes 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index 3705fd7b..6e6ea374 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -68,9 +68,9 @@ internal enum L10n { internal static let genres = L10n.tr("Localizable", "genres") /// Home internal static let home = L10n.tr("Localizable", "home") - /// Latest in %@ - internal static func latestInWithString(_ p1: Any) -> String { - return L10n.tr("Localizable", "latestInWithString", String(describing: p1)) + /// Latest %@ + internal static func latestWithString(_ p1: Any) -> String { + return L10n.tr("Localizable", "latestWithString", String(describing: p1)) } /// Library internal static let library = L10n.tr("Localizable", "library") diff --git a/Swiftfin tvOS/Views/LatestMediaView.swift b/Swiftfin tvOS/Views/LatestMediaView.swift index cc5e96a7..68fbe395 100644 --- a/Swiftfin tvOS/Views/LatestMediaView.swift +++ b/Swiftfin tvOS/Views/LatestMediaView.swift @@ -18,7 +18,7 @@ struct LatestMediaView: View { var body: some View { VStack(alignment: .leading) { - L10n.latestInWithString(viewModel.library.name ?? "").text + L10n.latestWithString(viewModel.library.name ?? "").text .font(.title3) .padding(.horizontal, 50) diff --git a/Swiftfin/Views/HomeView.swift b/Swiftfin/Views/HomeView.swift index e81881f0..5fbb9378 100644 --- a/Swiftfin/Views/HomeView.swift +++ b/Swiftfin/Views/HomeView.swift @@ -81,7 +81,7 @@ struct HomeView: View { LatestMediaView(viewModel: LatestMediaViewModel(library: library)) { HStack { - Text(L10n.latestInWithString(library.name ?? "")) + Text(L10n.latestWithString(library.name ?? "")) .font(.title2) .fontWeight(.bold) diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index d5a9e33877e34f865afc95d4ed87707eee7c9cc0..a22e6cfc0557333da07933dee274ee9d36e13f32 100644 GIT binary patch delta 28 jcmeyO@=9gHC)UYIJaXJV42cXS45 Date: Sat, 8 Jan 2022 23:56:58 -0700 Subject: [PATCH 10/18] modal item transition --- Swiftfin tvOS/Views/HomeView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Swiftfin tvOS/Views/HomeView.swift b/Swiftfin tvOS/Views/HomeView.swift index 78fcbfa5..17f91ca8 100644 --- a/Swiftfin tvOS/Views/HomeView.swift +++ b/Swiftfin tvOS/Views/HomeView.swift @@ -47,7 +47,7 @@ struct HomeView: View { PortraitItemsRowView(rowTitle: "Recently Added", items: viewModel.latestAddedItems, showItemTitles: showPosterLabels) { item in - homeRouter.route(to: \.item, item) + homeRouter.route(to: \.modalItem, item) } } From ddc6914e04c13d43f574f7aa0be707bf3561de42 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sun, 9 Jan 2022 00:13:05 -0700 Subject: [PATCH 11/18] organize files --- Swiftfin.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 9c5b8dd2..428784fe 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -977,7 +977,7 @@ isa = PBXGroup; children = ( E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */, - E13F26B22787597300DF4761 /* SingleSeasonEpisodesRowView.swift */, + E103A6A1278A7EB500820EC7 /* HomeCinematicView */, E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */, 531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */, E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */, @@ -987,7 +987,7 @@ E1E5D5412783B33900692DFE /* PortraitItemsRowView.swift */, 536D3D87267C17350004248C /* PublicUserButton.swift */, E17885A3278105170094FBCF /* SFSymbolButton.swift */, - E103A6A1278A7EB500820EC7 /* HomeCinematicView */, + E13F26B22787597300DF4761 /* SingleSeasonEpisodesRowView.swift */, ); path = Components; sourceTree = ""; From 188e09c22c0ed8eaad0b0725d103eee704c91317 Mon Sep 17 00:00:00 2001 From: koen Date: Sun, 9 Jan 2022 18:48:31 +0100 Subject: [PATCH 12/18] Refresh homeview automatically --- Swiftfin/Objects/RefreshHelper.swift | 19 ++++++++++++++++++- Swiftfin/Views/HomeView.swift | 3 +++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Swiftfin/Objects/RefreshHelper.swift b/Swiftfin/Objects/RefreshHelper.swift index 6c8cceb8..d3947853 100644 --- a/Swiftfin/Objects/RefreshHelper.swift +++ b/Swiftfin/Objects/RefreshHelper.swift @@ -11,9 +11,10 @@ import UIKit // A more general derivative of // https://stackoverflow.com/questions/65812080/introspect-library-uirefreshcontrol-with-swiftui-not-working -class RefreshHelper { +final class RefreshHelper { var refreshControl: UIRefreshControl? var refreshAction: (() -> Void)? + private var lastAutomaticRefresh = Date() @objc func didRefresh() { guard let refreshControl = refreshControl else { return } @@ -21,3 +22,19 @@ class RefreshHelper { refreshControl.endRefreshing() } } + +// MARK: - automatic refreshing + +extension RefreshHelper { + private static let timeUntilStale = TimeInterval(60) + + func refreshStaleData() { + guard isStale else { return } + lastAutomaticRefresh = .now + refreshAction?() + } + + private var isStale: Bool { + lastAutomaticRefresh.addingTimeInterval(Self.timeUntilStale) < .now + } +} diff --git a/Swiftfin/Views/HomeView.swift b/Swiftfin/Views/HomeView.swift index 16274325..7520a521 100644 --- a/Swiftfin/Views/HomeView.swift +++ b/Swiftfin/Views/HomeView.swift @@ -119,5 +119,8 @@ struct HomeView: View { } } } + .onAppear { + refreshHelper.refreshStaleData() + } } } From 63ba81e916262adc3b868f3ba8be651606a754f4 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sun, 9 Jan 2022 13:37:24 -0700 Subject: [PATCH 13/18] adjustment and comment --- .../Components/HomeCinematicView/HomeCinematicView.swift | 2 ++ .../HomeCinematicView/UICinematicBackgroundView.swift | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift b/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift index 37edaa8d..b023dcb4 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift @@ -11,6 +11,8 @@ import SwiftUI import UIKit import JellyfinAPI +// TODO: Generalize this view such that it can be used in other contexts like for a library + struct HomeCinematicViewItem: Hashable { enum TopRowType { diff --git a/Swiftfin tvOS/Components/HomeCinematicView/UICinematicBackgroundView.swift b/Swiftfin tvOS/Components/HomeCinematicView/UICinematicBackgroundView.swift index 86c77b11..420416b9 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/UICinematicBackgroundView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/UICinematicBackgroundView.swift @@ -41,7 +41,7 @@ class UICinematicBackgroundView: UIView { selectDelayTimer?.invalidate() - selectDelayTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(delayTimerTimed), userInfo: imageView, repeats: false) + selectDelayTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(delayTimerTimed), userInfo: imageView, repeats: false) } From 14c8aa41010c2a1a0956034f047ec1e09829befd Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sun, 9 Jan 2022 18:22:38 -0700 Subject: [PATCH 14/18] create general poster size --- .../ItemViewModel/ItemViewModel.swift | 15 +++++++------- Shared/Views/PortraitItemSize.swift | 19 ++++++++++++++++++ Swiftfin.xcodeproj/project.pbxproj | 20 ++++++++++++------- .../EpisodesRowView.swift | 0 Swiftfin/Components/PortraitHStackView.swift | 3 +-- .../ItemPortraitHeaderOverlayView.swift | 3 +-- 6 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 Shared/Views/PortraitItemSize.swift rename Swiftfin/{Views/ItemView => Components}/EpisodesRowView.swift (100%) diff --git a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift index 85ead04a..cd06f9da 100644 --- a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift @@ -17,14 +17,9 @@ class ItemViewModel: ViewModel { @Published var item: BaseItemDto @Published var playButtonItem: BaseItemDto? { didSet { - playButtonItem?.createVideoPlayerViewModel() - .sink { completion in - self.handleAPIRequestError(completion: completion) - } receiveValue: { videoPlayerViewModel in - self.itemVideoPlayerViewModel = videoPlayerViewModel - self.mediaItems = videoPlayerViewModel.item.createMediaItems() - } - .store(in: &cancellables) + if let playButtonItem = playButtonItem { + refreshItemVideoPlayerViewModel(for: playButtonItem) + } } } @Published var similarItems: [BaseItemDto] = [] @@ -52,6 +47,10 @@ class ItemViewModel: ViewModel { getSimilarItems() + refreshItemVideoPlayerViewModel(for: item) + } + + func refreshItemVideoPlayerViewModel(for item: BaseItemDto) { item.createVideoPlayerViewModel() .sink { completion in self.handleAPIRequestError(completion: completion) diff --git a/Shared/Views/PortraitItemSize.swift b/Shared/Views/PortraitItemSize.swift new file mode 100644 index 00000000..62cf5241 --- /dev/null +++ b/Shared/Views/PortraitItemSize.swift @@ -0,0 +1,19 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import SwiftUI + +extension View { + + /// Applies Portrait Poster frame with proper corner radius ratio against the width + func portraitPoster(width: CGFloat) -> some View { + self.frame(width: width, height: width * 1.5) + .cornerRadius(width / 24) + } +} diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 428784fe..2779af7c 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -254,6 +254,8 @@ E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */; }; E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */; }; E107BB972788104100354E07 /* CinematicCollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB952788104100354E07 /* CinematicCollectionItemView.swift */; }; + E10C0941278B8DAB009DBF93 /* PortraitItemSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10C0940278B8DAB009DBF93 /* PortraitItemSize.swift */; }; + E10C0942278B8DAB009DBF93 /* PortraitItemSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10C0940278B8DAB009DBF93 /* PortraitItemSize.swift */; }; E10D87DA2784E4F100BD264C /* ItemViewDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10D87D92784E4F100BD264C /* ItemViewDetailsView.swift */; }; E10D87DC2784EC5200BD264C /* EpisodesRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10D87DB2784EC5200BD264C /* EpisodesRowView.swift */; }; E10D87DE278510E400BD264C /* PosterSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10D87DD278510E300BD264C /* PosterSize.swift */; }; @@ -650,6 +652,7 @@ E103A6A8278AB6FF00820EC7 /* CinematicNextUpCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicNextUpCardView.swift; sourceTree = ""; }; E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemViewModel.swift; sourceTree = ""; }; E107BB952788104100354E07 /* CinematicCollectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicCollectionItemView.swift; sourceTree = ""; }; + E10C0940278B8DAB009DBF93 /* PortraitItemSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemSize.swift; sourceTree = ""; }; E10D87D92784E4F100BD264C /* ItemViewDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewDetailsView.swift; sourceTree = ""; }; E10D87DB2784EC5200BD264C /* EpisodesRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesRowView.swift; sourceTree = ""; }; E10D87DD278510E300BD264C /* PosterSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterSize.swift; sourceTree = ""; }; @@ -1193,6 +1196,7 @@ isa = PBXGroup; children = ( E188460326DEF04800B0C5B7 /* EpisodeCardVStackView.swift */, + E10D87DB2784EC5200BD264C /* EpisodesRowView.swift */, E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */, E1AD105526D981CE003E4A08 /* PortraitHStackView.swift */, C4BE076D2720FEA8003F4AD1 /* PortraitItemElement.swift */, @@ -1453,7 +1457,6 @@ E14F7D0A26DB3714007C3AE6 /* ItemView */ = { isa = PBXGroup; children = ( - E10D87DB2784EC5200BD264C /* EpisodesRowView.swift */, 535BAE9E2649E569005FA86D /* ItemView.swift */, E18845F726DEA9C900B0C5B7 /* ItemViewBody.swift */, E10D87D92784E4F100BD264C /* ItemViewDetailsView.swift */, @@ -1550,12 +1553,13 @@ E1AD105326D96F5A003E4A08 /* Views */ = { isa = PBXGroup; children = ( - C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */, - 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */, 531AC8BE26750DE20091C7EB /* ImageView.swift */, 621338B22660A07800A81A2A /* LazyView.swift */, + C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */, 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */, 6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */, + 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */, + E10C0940278B8DAB009DBF93 /* PortraitItemSize.swift */, 624C21742685CF60007F1390 /* SearchablePickerView.swift */, 53DE4BD1267098F300739748 /* SearchBarView.swift */, ); @@ -2195,6 +2199,7 @@ 6264E88D273850380081A12A /* Strings.swift in Sources */, 536D3D76267BA9BB0004248C /* MainTabViewModel.swift in Sources */, E193D5512719432400900D82 /* ServerDetailViewModel.swift in Sources */, + E10C0942278B8DAB009DBF93 /* PortraitItemSize.swift in Sources */, C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */, E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */, E1E5D5392783A56B00692DFE /* EpisodesRowView.swift in Sources */, @@ -2319,6 +2324,7 @@ E10D87DC2784EC5200BD264C /* EpisodesRowView.swift in Sources */, E1C812BE277A8E5D00918266 /* VLCPlayerViewController.swift in Sources */, E1AA331D2782541500F6439C /* PrimaryButtonView.swift in Sources */, + E10C0941278B8DAB009DBF93 /* PortraitItemSize.swift in Sources */, 62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */, 531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */, C4BE07762725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift in Sources */, @@ -2745,7 +2751,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2782,7 +2788,7 @@ CURRENT_PROJECT_VERSION = 66; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2813,7 +2819,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2840,7 +2846,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Swiftfin/Views/ItemView/EpisodesRowView.swift b/Swiftfin/Components/EpisodesRowView.swift similarity index 100% rename from Swiftfin/Views/ItemView/EpisodesRowView.swift rename to Swiftfin/Components/EpisodesRowView.swift diff --git a/Swiftfin/Components/PortraitHStackView.swift b/Swiftfin/Components/PortraitHStackView.swift index d2c55051..30324336 100644 --- a/Swiftfin/Components/PortraitHStackView.swift +++ b/Swiftfin/Components/PortraitHStackView.swift @@ -46,8 +46,7 @@ struct PortraitImageHStackView Date: Sun, 9 Jan 2022 20:00:41 -0700 Subject: [PATCH 15/18] iOS truncated item overview --- Shared/Coordinators/ItemCoordinator.swift | 5 + .../ItemOverviewCoordinator.swift | 29 +++++ Shared/Extensions/ColorExtension.swift | 1 + Swiftfin.xcodeproj/project.pbxproj | 22 +++- Swiftfin/Components/TruncatedTextView.swift | 106 ++++++++++++++++++ Swiftfin/Views/ItemOverviewView.swift | 35 ++++++ Swiftfin/Views/ItemView/ItemViewBody.swift | 26 ++++- 7 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 Shared/Coordinators/ItemOverviewCoordinator.swift create mode 100644 Swiftfin/Components/TruncatedTextView.swift create mode 100644 Swiftfin/Views/ItemOverviewView.swift diff --git a/Shared/Coordinators/ItemCoordinator.swift b/Shared/Coordinators/ItemCoordinator.swift index 6c9e6c44..f9d13164 100644 --- a/Shared/Coordinators/ItemCoordinator.swift +++ b/Shared/Coordinators/ItemCoordinator.swift @@ -19,6 +19,7 @@ final class ItemCoordinator: NavigationCoordinatable { @Root var start = makeStart @Route(.push) var item = makeItem @Route(.push) var library = makeLibrary + @Route(.modal) var itemOverview = makeItemOverview @Route(.fullScreen) var videoPlayer = makeVideoPlayer let itemDto: BaseItemDto @@ -34,6 +35,10 @@ final class ItemCoordinator: NavigationCoordinatable { func makeItem(item: BaseItemDto) -> ItemCoordinator { ItemCoordinator(item: item) } + + func makeItemOverview(item: BaseItemDto) -> NavigationViewCoordinator { + NavigationViewCoordinator(ItemOverviewCoordinator(item: itemDto)) + } func makeVideoPlayer(viewModel: VideoPlayerViewModel) -> NavigationViewCoordinator { NavigationViewCoordinator(VideoPlayerCoordinator(viewModel: viewModel)) diff --git a/Shared/Coordinators/ItemOverviewCoordinator.swift b/Shared/Coordinators/ItemOverviewCoordinator.swift new file mode 100644 index 00000000..cf80b4f9 --- /dev/null +++ b/Shared/Coordinators/ItemOverviewCoordinator.swift @@ -0,0 +1,29 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Stinsen +import SwiftUI +import JellyfinAPI + +final class ItemOverviewCoordinator: NavigationCoordinatable { + + let stack = NavigationStack(initial: \ItemOverviewCoordinator.start) + + @Root var start = makeStart + + let item: BaseItemDto + + init(item: BaseItemDto) { + self.item = item + } + + @ViewBuilder func makeStart() -> some View { + ItemOverviewView(item: item) + } +} diff --git a/Shared/Extensions/ColorExtension.swift b/Shared/Extensions/ColorExtension.swift index edc56d4c..b5869500 100644 --- a/Shared/Extensions/ColorExtension.swift +++ b/Shared/Extensions/ColorExtension.swift @@ -20,6 +20,7 @@ extension Color { public static let lightGray = Color(UIColor.lightGray) #else public static let systemFill = Color(UIColor.systemFill) + public static let systemBackground = Color(UIColor.systemBackground) public static let secondarySystemFill = Color(UIColor.secondarySystemBackground) public static let tertiarySystemFill = Color(UIColor.tertiarySystemBackground) #endif diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 2779af7c..d0ced680 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -430,6 +430,9 @@ E1E5D54F2783E67100692DFE /* OverlaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D54E2783E67100692DFE /* OverlaySettingsView.swift */; }; E1E5D5512783E67700692DFE /* ExperimentalSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5502783E67700692DFE /* ExperimentalSettingsView.swift */; }; E1E5D553278419D900692DFE /* ConfirmCloseOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D552278419D900692DFE /* ConfirmCloseOverlay.swift */; }; + E1EBCB42278BD174009FE6E9 /* TruncatedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */; }; + E1EBCB44278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */; }; + E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */; }; E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1FA2F7427818A8800B4C270 /* SmallMenuOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FA2F7327818A8800B4C270 /* SmallMenuOverlay.swift */; }; @@ -750,6 +753,9 @@ E1E5D54E2783E67100692DFE /* OverlaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlaySettingsView.swift; sourceTree = ""; }; E1E5D5502783E67700692DFE /* ExperimentalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentalSettingsView.swift; sourceTree = ""; }; E1E5D552278419D900692DFE /* ConfirmCloseOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmCloseOverlay.swift; sourceTree = ""; }; + E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TruncatedTextView.swift; sourceTree = ""; }; + E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemOverviewCoordinator.swift; sourceTree = ""; }; + E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemOverviewView.swift; sourceTree = ""; }; E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = ""; }; E1FA2F7327818A8800B4C270 /* SmallMenuOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallMenuOverlay.swift; sourceTree = ""; }; E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; @@ -1202,6 +1208,7 @@ C4BE076D2720FEA8003F4AD1 /* PortraitItemElement.swift */, 53F866432687A45F00DCD1D7 /* PortraitItemView.swift */, E1AA331C2782541500F6439C /* PrimaryButtonView.swift */, + E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */, ); path = Components; sourceTree = ""; @@ -1261,11 +1268,12 @@ 6220D0B926D6092100B8E046 /* FilterCoordinator.swift */, 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */, 6220D0BF26D61C5000B8E046 /* ItemCoordinator.swift */, + E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */, 6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */, + 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */, + C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */, C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */, C4BE07782726EE82003F4AD1 /* LiveTVTabCoordinator.swift */, - C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */, - 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */, E193D5412719404B00900D82 /* MainCoordinator */, C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */, 6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */, @@ -1424,18 +1432,19 @@ 5338F74D263B61370014BF09 /* ConnectToServerView.swift */, 5389276D263C25100035E14B /* ContinueWatchingView.swift */, 625CB56E2678C23300530A6E /* HomeView.swift */, + E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */, E14F7D0A26DB3714007C3AE6 /* ItemView */, 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */, - C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */, - C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */, 53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */, 6213388F265F83A900A81A2A /* LibraryListView.swift */, 53EE24E5265060780068F029 /* LibrarySearchView.swift */, 53DF641D263D9C0600A7CD1A /* LibraryView.swift */, + C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */, + C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */, 5389276F263C25230035E14B /* NextUpView.swift */, - E1E5D54A2783E26100692DFE /* SettingsView */, E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */, E13DD3E427177D15009D4DAF /* ServerListView.swift */, + E1E5D54A2783E26100692DFE /* SettingsView */, E13DD3FB2717EAE8009D4DAF /* UserListView.swift */, E13DD3F4271793BB009D4DAF /* UserSignInView.swift */, E193D5452719418B00900D82 /* VideoPlayer */, @@ -2240,6 +2249,7 @@ 53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */, E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */, E1C812C5277A90B200918266 /* URLComponentsExtensions.swift in Sources */, + E1EBCB44278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift in Sources */, 62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */, 625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */, 62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */, @@ -2270,6 +2280,7 @@ E19169CE272514760085832A /* HTTPScheme.swift in Sources */, 53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */, C4AE2C3027498D2300AE13CF /* LiveTVHomeView.swift in Sources */, + E1EBCB42278BD174009FE6E9 /* TruncatedTextView.swift in Sources */, 62133890265F83A900A81A2A /* LibraryListView.swift in Sources */, 62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */, 62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */, @@ -2342,6 +2353,7 @@ 62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */, 62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */, C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */, + E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */, 091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */, 62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */, 5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */, diff --git a/Swiftfin/Components/TruncatedTextView.swift b/Swiftfin/Components/TruncatedTextView.swift new file mode 100644 index 00000000..a61f49d6 --- /dev/null +++ b/Swiftfin/Components/TruncatedTextView.swift @@ -0,0 +1,106 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import SwiftUI + +struct TruncatedTextView: View { + + @State private var truncated: Bool = false + @State private var shrinkText: String + private var text: String + let font: UIFont + let lineLimit: Int + let seeMoreAction: () -> Void + + private var moreLessText: String { + if !truncated { + return "" + } else { + return "See More" + } + } + + init(_ text: String, + lineLimit: Int, + font: UIFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body), + seeMoreAction: @escaping () -> Void) { + self.text = text + self.lineLimit = lineLimit + _shrinkText = State(wrappedValue: text) + self.font = font + self.seeMoreAction = seeMoreAction + } + + var body: some View { + VStack(alignment: .center) { + Group { + Text(shrinkText) + .overlay { + if truncated { + LinearGradient(stops: [.init(color: .systemBackground.opacity(0), location: 0.5), + .init(color: .systemBackground.opacity(0.8), location: 0.7), + .init(color: .systemBackground, location: 1)], + startPoint: .top, + endPoint: .bottom) + } + } + } + .lineLimit(lineLimit) + .background { + // Render the limited text and measure its size + Text(text) + .lineLimit(lineLimit + 2) + .background { + GeometryReader { visibleTextGeometry in + Color.clear + .onAppear { + let size = CGSize(width: visibleTextGeometry.size.width, height: .greatestFiniteMagnitude) + let attributes:[NSAttributedString.Key:Any] = [NSAttributedString.Key.font: font] + var low = 0 + var heigh = shrinkText.count + var mid = heigh + while ((heigh - low) > 1) { + let attributedText = NSAttributedString(string: shrinkText, attributes: attributes) + let boundingRect = attributedText.boundingRect(with: size, options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil) + if boundingRect.size.height > visibleTextGeometry.size.height { + truncated = true + heigh = mid + mid = (heigh + low)/2 + + } else { + if mid == text.count { + break + } else { + low = mid + mid = (low + heigh)/2 + } + } + shrinkText = String(text.prefix(mid)) + } + + if truncated { + shrinkText = String(shrinkText.prefix(shrinkText.count - 2)) + } + } + } + } + .hidden() + } + .font(Font(font)) + + if truncated { + Button { + seeMoreAction() + } label: { + Text(moreLessText) + } + } + } + } +} diff --git a/Swiftfin/Views/ItemOverviewView.swift b/Swiftfin/Views/ItemOverviewView.swift new file mode 100644 index 00000000..f155ae34 --- /dev/null +++ b/Swiftfin/Views/ItemOverviewView.swift @@ -0,0 +1,35 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import JellyfinAPI +import SwiftUI + +struct ItemOverviewView: View { + + @EnvironmentObject var itemOverviewRouter: ItemOverviewCoordinator.Router + let item: BaseItemDto + + var body: some View { + ScrollView(showsIndicators: false) { + Text(item.overview ?? "") + .font(.footnote) + .padding() + } + .navigationBarTitle("Overview", displayMode: .inline) + .toolbar { + ToolbarItemGroup(placement: .navigationBarLeading) { + Button { + itemOverviewRouter.dismissCoordinator() + } label: { + Image(systemName: "xmark.circle.fill") + } + } + } + } +} diff --git a/Swiftfin/Views/ItemView/ItemViewBody.swift b/Swiftfin/Views/ItemView/ItemViewBody.swift index e3860e3e..548a20ac 100644 --- a/Swiftfin/Views/ItemView/ItemViewBody.swift +++ b/Swiftfin/Views/ItemView/ItemViewBody.swift @@ -13,6 +13,8 @@ import SwiftUI struct ItemViewBody: View { + @Environment(\.horizontalSizeClass) private var hSizeClass + @Environment(\.verticalSizeClass) private var vSizeClass @EnvironmentObject var itemRouter: ItemCoordinator.Router @EnvironmentObject private var viewModel: ItemViewModel @Default(.showCastAndCrew) var showCastAndCrew @@ -20,11 +22,25 @@ struct ItemViewBody: View { var body: some View { VStack(alignment: .leading) { // MARK: Overview - - Text(viewModel.item.overview ?? "") - .font(.footnote) - .padding(.horizontal, 16) - .padding(.vertical, 3) + + if let itemOverview = viewModel.item.overview { + if hSizeClass == .compact && vSizeClass == .regular { + TruncatedTextView(itemOverview, + lineLimit: 5, + font: UIFont.preferredFont(forTextStyle: .footnote)) { + itemRouter.route(to: \.itemOverview, viewModel.item) + } + .padding() + } else { + Text(itemOverview) + .font(.footnote) + .padding() + } + } else { + Text("No overview available") + .font(.footnote) + .padding() + } // MARK: Seasons From e3608e9ca153c50ecdead6ea4d8d0ab32c31cfad Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sun, 9 Jan 2022 21:02:02 -0700 Subject: [PATCH 16/18] ios current episode bordered on row view and see more description --- Shared/Coordinators/ItemOverviewCoordinator.swift | 4 ++++ Swiftfin.xcodeproj/project.pbxproj | 4 ++++ Swiftfin/Components/EpisodesRowView.swift | 7 +++++++ Swiftfin/Views/ItemView/ItemViewBody.swift | 3 ++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Shared/Coordinators/ItemOverviewCoordinator.swift b/Shared/Coordinators/ItemOverviewCoordinator.swift index cf80b4f9..870109ec 100644 --- a/Shared/Coordinators/ItemOverviewCoordinator.swift +++ b/Shared/Coordinators/ItemOverviewCoordinator.swift @@ -24,6 +24,10 @@ final class ItemOverviewCoordinator: NavigationCoordinatable { } @ViewBuilder func makeStart() -> some View { + #if os(tvOS) + EmptyView() + #else ItemOverviewView(item: item) + #endif } } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index d0ced680..24b84283 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -433,6 +433,8 @@ E1EBCB42278BD174009FE6E9 /* TruncatedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */; }; E1EBCB44278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */; }; E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */; }; + E1EBCB4A278BE443009FE6E9 /* ItemOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */; }; + E1EBCB4B278BE5BC009FE6E9 /* ItemOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */; }; E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1FA2F7427818A8800B4C270 /* SmallMenuOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FA2F7327818A8800B4C270 /* SmallMenuOverlay.swift */; }; @@ -2093,6 +2095,7 @@ E1E5D5462783C28100692DFE /* CinematicItemAboutView.swift in Sources */, E1FCD09726C47118007C8DCF /* ErrorMessage.swift in Sources */, E193D53527193F8100900D82 /* ItemCoordinator.swift in Sources */, + E1EBCB4A278BE443009FE6E9 /* ItemOverviewCoordinator.swift in Sources */, 53116A19268B947A003024C9 /* PlainLinkButton.swift in Sources */, C4BE07772725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift in Sources */, 536D3D88267C17350004248C /* PublicUserButton.swift in Sources */, @@ -2127,6 +2130,7 @@ 535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */, 53A83C33268A309300DF3D92 /* LibraryView.swift in Sources */, 62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */, + E1EBCB4B278BE5BC009FE6E9 /* ItemOverviewView.swift in Sources */, 5398514526B64DA100101B49 /* SettingsView.swift in Sources */, 62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */, E193D54B271941D300900D82 /* ServerListView.swift in Sources */, diff --git a/Swiftfin/Components/EpisodesRowView.swift b/Swiftfin/Components/EpisodesRowView.swift index 605a9142..aee0e7c7 100644 --- a/Swiftfin/Components/EpisodesRowView.swift +++ b/Swiftfin/Components/EpisodesRowView.swift @@ -106,6 +106,13 @@ struct EpisodesRowView: View { bh: episode.getBackdropImageBlurHash()) .mask(Rectangle().frame(width: 200, height: 112).cornerRadius(10)) .frame(width: 200, height: 112) + .overlay { + if episode.id == viewModel.episodeItemViewModel.item.id { + RoundedRectangle(cornerRadius: 6) + .stroke(Color.jellyfinPurple, lineWidth: 4) + } + } + .padding(.top) VStack(alignment: .leading) { Text(episode.getEpisodeLocator() ?? "") diff --git a/Swiftfin/Views/ItemView/ItemViewBody.swift b/Swiftfin/Views/ItemView/ItemViewBody.swift index 548a20ac..a7ed4e8d 100644 --- a/Swiftfin/Views/ItemView/ItemViewBody.swift +++ b/Swiftfin/Views/ItemView/ItemViewBody.swift @@ -30,7 +30,8 @@ struct ItemViewBody: View { font: UIFont.preferredFont(forTextStyle: .footnote)) { itemRouter.route(to: \.itemOverview, viewModel.item) } - .padding() + .padding(.horizontal) + .padding(.top) } else { Text(itemOverview) .font(.footnote) From 03aeed967ed50f46732bce116e75a93634d82e5d Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sun, 9 Jan 2022 21:13:35 -0700 Subject: [PATCH 17/18] tvos cleanup --- Shared/Views/PortraitItemSize.swift | 2 +- .../CinematicItemView/CinematicItemAboutView.swift | 7 +++---- Swiftfin.xcodeproj/project.pbxproj | 2 -- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Shared/Views/PortraitItemSize.swift b/Shared/Views/PortraitItemSize.swift index 62cf5241..3af53228 100644 --- a/Shared/Views/PortraitItemSize.swift +++ b/Shared/Views/PortraitItemSize.swift @@ -14,6 +14,6 @@ extension View { /// Applies Portrait Poster frame with proper corner radius ratio against the width func portraitPoster(width: CGFloat) -> some View { self.frame(width: width, height: width * 1.5) - .cornerRadius(width / 24) + .cornerRadius((width * 1.5) / 40) } } diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemAboutView.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemAboutView.swift index 2b417eab..89977d74 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemAboutView.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemAboutView.swift @@ -17,13 +17,12 @@ struct CinematicItemAboutView: View { var body: some View { HStack(alignment: .top, spacing: 10) { ImageView(src: viewModel.item.portraitHeaderViewURL(maxWidth: 257)) - .frame(width: 257, height: 380) - .cornerRadius(10) + .portraitPoster(width: 257) ZStack(alignment: .topLeading) { Color(UIColor.darkGray).opacity(focused ? 0.2 : 0) - .cornerRadius(30) - .frame(height: 380) + .cornerRadius(9.5) + .frame(height: 385.5) VStack(alignment: .leading) { Text("About") diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 24b84283..2a64794f 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -434,7 +434,6 @@ E1EBCB44278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */; }; E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */; }; E1EBCB4A278BE443009FE6E9 /* ItemOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */; }; - E1EBCB4B278BE5BC009FE6E9 /* ItemOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */; }; E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1FA2F7427818A8800B4C270 /* SmallMenuOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FA2F7327818A8800B4C270 /* SmallMenuOverlay.swift */; }; @@ -2130,7 +2129,6 @@ 535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */, 53A83C33268A309300DF3D92 /* LibraryView.swift in Sources */, 62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */, - E1EBCB4B278BE5BC009FE6E9 /* ItemOverviewView.swift in Sources */, 5398514526B64DA100101B49 /* SettingsView.swift in Sources */, 62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */, E193D54B271941D300900D82 /* ServerListView.swift in Sources */, From 371f59c8c88728ac95866fd496dbb9b74976ab28 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sun, 9 Jan 2022 21:18:26 -0700 Subject: [PATCH 18/18] remove dev team --- Swiftfin.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 2a64794f..698d1745 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -2765,7 +2765,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2802,7 +2802,7 @@ CURRENT_PROJECT_VERSION = 66; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2833,7 +2833,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2860,7 +2860,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = (