Fix iOS Chapter Overlay (#992)

This commit is contained in:
Ethan Pippin 2024-03-13 23:08:43 -06:00 committed by GitHub
parent 876ffba417
commit 1bd18ef8b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 201 additions and 211 deletions

View File

@ -30,6 +30,9 @@ jobs:
- name: Install SwiftGen
run: brew install swiftgen
- name: Set Xcode Version
run: sudo xcode-select -s "/Applications/Xcode_15.2.app"
- name: Cache Carthage
uses: actions/cache@v4
id: carthage-cache
@ -41,12 +44,14 @@ jobs:
- name: Update Carthage
run: carthage update --use-xcframeworks --cache-builds
- name: Cache Swift packages
uses: actions/cache@v4
with:
path: PackageCache
key: ${{ runner.os }}-${{ matrix.scheme }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: ${{ runner.os }}-${{ matrix.scheme }}-spm-
# FIXME: caches would keep failed compiles?
# - name: Cache Swift packages
# uses: actions/cache@v4
# with:
# path: PackageCache
# key: ${{ runner.os }}-${{ matrix.scheme }}-spm-${{ hashFiles('**/Package.resolved') }}
# restore-keys: ${{ runner.os }}-${{ matrix.scheme }}-spm-
- name: Build
uses: nick-fields/retry@v2

View File

@ -8,27 +8,29 @@
import SwiftUI
struct TypeSystemNameView<Item: Poster>: View {
struct SystemImageContentView: View {
@State
private var contentSize: CGSize = .zero
let item: Item
private let systemName: String
init(systemName: String?) {
self.systemName = systemName ?? "circle"
}
var body: some View {
ZStack {
Color.secondarySystemFill
.opacity(0.5)
if let typeSystemImage = item.typeSystemName {
Image(systemName: typeSystemImage)
Image(systemName: systemName)
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.secondary)
.accessibilityHidden(true)
.frame(width: contentSize.width / 3.5, height: contentSize.height / 3)
}
}
.size($contentSize)
}
}

View File

@ -35,18 +35,13 @@ extension EnvironmentValues {
static let defaultValue: Binding<Double> = .constant(1)
}
// TODO: does this actually do anything useful?
// should instead use view safe area?
// TODO: See if we can use a root `GeometryReader` that sets the environment value
struct SafeAreaInsetsKey: EnvironmentKey {
static var defaultValue: EdgeInsets {
UIApplication.shared.keyWindow?.safeAreaInsets.asEdgeInsets ?? .zero
}
}
struct ShowsLibraryFiltersKey: EnvironmentKey {
static let defaultValue: Bool = true
}
struct SubtitleOffsetKey: EnvironmentKey {
static let defaultValue: Binding<Int> = .constant(0)
}

View File

@ -49,13 +49,6 @@ extension EnvironmentValues {
self[SafeAreaInsetsKey.self]
}
// TODO: remove and make a parameter instead, isn't necessarily an
// environment value
var showsLibraryFilters: Bool {
get { self[ShowsLibraryFiltersKey.self] }
set { self[ShowsLibraryFiltersKey.self] = newValue }
}
var subtitleOffset: Binding<Int> {
get { self[SubtitleOffsetKey.self] }
set { self[SubtitleOffsetKey.self] = newValue }

View File

@ -44,7 +44,7 @@ extension BaseItemDto: Poster {
}
}
var typeSystemName: String? {
var typeSystemImage: String? {
switch type {
case .episode, .movie, .series:
"film"

View File

@ -21,7 +21,7 @@ extension BaseItemPerson: Poster {
true
}
var typeSystemName: String? {
var typeSystemImage: String? {
"person.fill"
}

View File

@ -31,7 +31,7 @@ extension ChapterInfo {
extension ChapterInfo {
struct FullInfo: Poster {
struct FullInfo: Poster, Equatable {
var id: Int {
chapterInfo.hashValue
@ -45,7 +45,7 @@ extension ChapterInfo {
chapterInfo.displayTitle
}
let typeSystemName: String? = "film"
let typeSystemImage: String? = "film"
var subtitle: String?
var showTitle: Bool = true

View File

@ -9,13 +9,13 @@
import Foundation
// TODO: remove `showTitle` and `subtitle` since the PosterButton can define custom supplementary views?
// TODO: instead of the below image functions, have variables that match `ImageType`
// TODO: instead of the below image functions, have functions that match `ImageType`
// - allows caller to choose images
protocol Poster: Displayable, Hashable, Identifiable {
var subtitle: String? { get }
var showTitle: Bool { get }
var typeSystemName: String? { get }
var typeSystemImage: String? { get }
func portraitPosterImageSource(maxWidth: CGFloat) -> ImageSource
func landscapePosterImageSources(maxWidth: CGFloat, single: Bool) -> [ImageSource]

View File

@ -16,8 +16,6 @@ struct CinematicItemSelector<Item: Poster>: View {
@State
private var focusedItem: Item?
@State
private var posterHStackSize: CGSize = .zero
@StateObject
private var viewModel: CinematicBackgroundView<Item>.ViewModel = .init()
@ -47,8 +45,6 @@ struct CinematicItemSelector<Item: Poster>: View {
.transition(.opacity)
}
// By design, PosterHStack/CollectionHStack requires being in a ScrollView
ScrollView {
PosterHStack(type: .landscape, items: items)
.content(itemContent)
.imageOverlay(itemImageOverlay)
@ -56,16 +52,6 @@ struct CinematicItemSelector<Item: Poster>: View {
.trailing(trailingContent)
.onSelect(onSelect)
.focusedItem($focusedItem)
.size($posterHStackSize)
}
.frame(height: posterHStackSize.height)
.if(true) { view in
if #available(tvOS 16, *) {
view.scrollDisabled(true)
} else {
view
}
}
}
}
.background(alignment: .top) {

View File

@ -30,19 +30,12 @@ struct PosterButton<Item: Poster>: View {
// Only set if desiring focus changes
private var onFocusChanged: ((Bool) -> Void)?
@ViewBuilder
private func poster(from item: Item) -> some View {
private func imageView(from item: Item) -> ImageView {
switch type {
case .portrait:
ImageView(item.portraitPosterImageSource(maxWidth: 500))
.failure {
TypeSystemNameView(item: item)
}
case .landscape:
ImageView(item.landscapePosterImageSources(maxWidth: 500, single: singleImage))
.failure {
TypeSystemNameView(item: item)
}
}
}
@ -54,7 +47,10 @@ struct PosterButton<Item: Poster>: View {
ZStack {
Color.clear
poster(from: item)
imageView(from: item)
.failure {
SystemImageContentView(systemName: item.typeSystemImage)
}
imageOverlay()
.eraseToAnyView()

View File

@ -58,8 +58,7 @@ struct PosterHStack<Item: Poster>: View {
}
.clipsToBounds(false)
.dataPrefix(20)
.horizontalInset(EdgeInsets.defaultEdgePadding)
.verticalInsets(top: 20, bottom: 20)
.insets(horizontal: EdgeInsets.defaultEdgePadding, vertical: 20)
.itemSpacing(EdgeInsets.defaultEdgePadding - 20)
.scrollBehavior(.continuousLeadingEdge)
}

View File

@ -111,7 +111,7 @@ extension SeriesEpisodeSelector {
EpisodeCard(episode: item)
.focused($focusedEpisodeID, equals: item.id)
}
.verticalInsets(top: 20, bottom: 20)
.insets(vertical: 20)
.mask {
VStack(spacing: 0) {
Color.white

View File

@ -180,9 +180,7 @@
E1047E2327E5880000CB0D4A /* TypeSystemNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1047E2227E5880000CB0D4A /* TypeSystemNameView.swift */; };
E104C870296E087200C1C3F9 /* IndicatorSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E104C86F296E087200C1C3F9 /* IndicatorSettingsView.swift */; };
E104C873296E0D0A00C1C3F9 /* IndicatorSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E104C872296E0D0A00C1C3F9 /* IndicatorSettingsView.swift */; };
E104DC8D2B9D8979008F506D /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E104DC8C2B9D8979008F506D /* CollectionHStack */; };
E104DC902B9D8995008F506D /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E104DC8F2B9D8995008F506D /* CollectionVGrid */; };
E104DC922B9D89A2008F506D /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E104DC912B9D89A2008F506D /* CollectionHStack */; };
E104DC942B9D89A2008F506D /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E104DC932B9D89A2008F506D /* CollectionVGrid */; };
E104DC962B9E7E29008F506D /* AssertionFailureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E104DC952B9E7E29008F506D /* AssertionFailureView.swift */; };
E104DC972B9E7E29008F506D /* AssertionFailureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E104DC952B9E7E29008F506D /* AssertionFailureView.swift */; };
@ -288,6 +286,9 @@
E1388A42293F0AAD009721B1 /* PreferenceUIHostingSwizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1388A40293F0AAD009721B1 /* PreferenceUIHostingSwizzling.swift */; };
E1388A43293F0AAD009721B1 /* PreferenceUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1388A41293F0AAD009721B1 /* PreferenceUIHostingController.swift */; };
E1388A46293F0ABA009721B1 /* SwizzleSwift in Frameworks */ = {isa = PBXBuildFile; productRef = E1388A45293F0ABA009721B1 /* SwizzleSwift */; };
E1392FED2BA218A80034110D /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = E1392FEC2BA218A80034110D /* SwiftUIIntrospect */; };
E1392FF22BA21B360034110D /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E1392FF12BA21B360034110D /* CollectionHStack */; };
E1392FF42BA21B470034110D /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E1392FF32BA21B470034110D /* CollectionHStack */; };
E139CC1D28EC836F00688DE2 /* ChapterOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E139CC1C28EC836F00688DE2 /* ChapterOverlay.swift */; };
E139CC1F28EC83E400688DE2 /* Int.swift in Sources */ = {isa = PBXBuildFile; fileRef = E139CC1E28EC83E400688DE2 /* Int.swift */; };
E13AF3B628A0C598009093AB /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E13AF3B528A0C598009093AB /* Nuke */; };
@ -429,6 +430,8 @@
E15D4F082B1B12C300442DB8 /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = E15D4F062B1B12C300442DB8 /* Backport.swift */; };
E15D4F0A2B1BD88900442DB8 /* Edge.swift in Sources */ = {isa = PBXBuildFile; fileRef = E15D4F092B1BD88900442DB8 /* Edge.swift */; };
E15D4F0B2B1BD88900442DB8 /* Edge.swift in Sources */ = {isa = PBXBuildFile; fileRef = E15D4F092B1BD88900442DB8 /* Edge.swift */; };
E15EFA842BA167350080E926 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E15EFA832BA167350080E926 /* CollectionHStack */; };
E15EFA862BA1685F0080E926 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = E15EFA852BA1685F0080E926 /* SwiftUIIntrospect */; };
E168BD10289A4162001A6922 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E168BD08289A4162001A6922 /* HomeView.swift */; };
E168BD13289A4162001A6922 /* ContinueWatchingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E168BD0D289A4162001A6922 /* ContinueWatchingView.swift */; };
E168BD14289A4162001A6922 /* LatestInLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E168BD0E289A4162001A6922 /* LatestInLibraryView.swift */; };
@ -1303,6 +1306,7 @@
62666E2327E501EB00EC0ECD /* Foundation.framework in Frameworks */,
62666E2127E501E400EC0ECD /* CoreVideo.framework in Frameworks */,
6220D0C926D63F3700B8E046 /* Stinsen in Frameworks */,
E1392FED2BA218A80034110D /* SwiftUIIntrospect in Frameworks */,
535870912669D7A800D05A09 /* Introspect in Frameworks */,
E13AF3B828A0C598009093AB /* NukeExtensions in Frameworks */,
E1575E58293E7685001665B1 /* Files in Frameworks */,
@ -1311,7 +1315,6 @@
E1B5F7AB29577BCE004B26CF /* PulseUI in Frameworks */,
E1B5F7A929577BCE004B26CF /* PulseLogHandler in Frameworks */,
62666E1B27E501D400EC0ECD /* CoreGraphics.framework in Frameworks */,
E104DC922B9D89A2008F506D /* CollectionHStack in Frameworks */,
E1388A46293F0ABA009721B1 /* SwizzleSwift in Frameworks */,
62666E2C27E5021000EC0ECD /* QuartzCore.framework in Frameworks */,
62666E1927E501D000EC0ECD /* CoreFoundation.framework in Frameworks */,
@ -1325,6 +1328,7 @@
E104DC942B9D89A2008F506D /* CollectionVGrid in Frameworks */,
62666E1527E501C800EC0ECD /* AVFoundation.framework in Frameworks */,
E13AF3BC28A0C59E009093AB /* BlurHashKit in Frameworks */,
E1392FF42BA21B470034110D /* CollectionHStack in Frameworks */,
62666E1327E501C300EC0ECD /* AudioToolbox.framework in Frameworks */,
E13AF3B628A0C598009093AB /* Nuke in Frameworks */,
E12186DE2718F1C50010884C /* Defaults in Frameworks */,
@ -1372,10 +1376,12 @@
E15210562946DF1B00375CC2 /* PulseLogHandler in Frameworks */,
62666E0427E5017500EC0ECD /* CoreText.framework in Frameworks */,
E13DD3C62716499E009D4DAF /* CoreStore in Frameworks */,
E1392FF22BA21B360034110D /* CollectionHStack in Frameworks */,
62666E0E27E501AF00EC0ECD /* Security.framework in Frameworks */,
E1DC9814296DC06200982F06 /* PulseLogHandler in Frameworks */,
E1DC9821296DDBE600982F06 /* CollectionView in Frameworks */,
E104DC8D2B9D8979008F506D /* CollectionHStack in Frameworks */,
E15EFA842BA167350080E926 /* CollectionHStack in Frameworks */,
E15EFA862BA1685F0080E926 /* SwiftUIIntrospect in Frameworks */,
62666DFE27E5015700EC0ECD /* AVFoundation.framework in Frameworks */,
62666DFD27E5014F00EC0ECD /* AudioToolbox.framework in Frameworks */,
E19E6E0528A0B958005C10C8 /* Nuke in Frameworks */,
@ -2897,8 +2903,9 @@
E18443CA2A037773002DDDC8 /* UDPBroadcast */,
E14CB6872A9FF71F001586C6 /* JellyfinAPI */,
E1A7B1642B9A9F7800152546 /* PreferencesView */,
E104DC912B9D89A2008F506D /* CollectionHStack */,
E104DC932B9D89A2008F506D /* CollectionVGrid */,
E1392FEC2BA218A80034110D /* SwiftUIIntrospect */,
E1392FF32BA21B470034110D /* CollectionHStack */,
);
productName = "JellyfinPlayer tvOS";
productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */;
@ -2946,8 +2953,10 @@
E15D4F042B1B0C3C00442DB8 /* PreferencesView */,
E113A2A62B5A178D009CAAAA /* CollectionHStack */,
E113A2A92B5A179A009CAAAA /* CollectionVGrid */,
E104DC8C2B9D8979008F506D /* CollectionHStack */,
E104DC8F2B9D8995008F506D /* CollectionVGrid */,
E15EFA832BA167350080E926 /* CollectionHStack */,
E15EFA852BA1685F0080E926 /* SwiftUIIntrospect */,
E1392FF12BA21B360034110D /* CollectionHStack */,
);
productName = JellyfinPlayer;
productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */;
@ -3017,8 +3026,8 @@
E1FAD1C42A0375BA007F5521 /* XCRemoteSwiftPackageReference "UDPBroadcastConnection" */,
E14CB6842A9FF62A001586C6 /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */,
E15D4F032B1B0C3C00442DB8 /* XCLocalSwiftPackageReference "PreferencesView" */,
E104DC8B2B9D8979008F506D /* XCRemoteSwiftPackageReference "CollectionHStack" */,
E104DC8E2B9D8995008F506D /* XCRemoteSwiftPackageReference "CollectionVGrid" */,
E1392FF02BA21B360034110D /* XCRemoteSwiftPackageReference "CollectionHStack" */,
);
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
projectDirPath = "";
@ -4261,14 +4270,6 @@
minimumVersion = 1.0.0;
};
};
E104DC8B2B9D8979008F506D /* XCRemoteSwiftPackageReference "CollectionHStack" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LePips/CollectionHStack";
requirement = {
branch = main;
kind = branch;
};
};
E104DC8E2B9D8995008F506D /* XCRemoteSwiftPackageReference "CollectionVGrid" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LePips/CollectionVGrid";
@ -4285,6 +4286,14 @@
minimumVersion = 2.0.0;
};
};
E1392FF02BA21B360034110D /* XCRemoteSwiftPackageReference "CollectionHStack" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LePips/CollectionHStack";
requirement = {
branch = main;
kind = branch;
};
};
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/JohnEstropia/CoreStore.git";
@ -4427,21 +4436,11 @@
package = E1002B662793CFBA00E47059 /* XCRemoteSwiftPackageReference "swift-algorithms" */;
productName = Algorithms;
};
E104DC8C2B9D8979008F506D /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
package = E104DC8B2B9D8979008F506D /* XCRemoteSwiftPackageReference "CollectionHStack" */;
productName = CollectionHStack;
};
E104DC8F2B9D8995008F506D /* CollectionVGrid */ = {
isa = XCSwiftPackageProductDependency;
package = E104DC8E2B9D8995008F506D /* XCRemoteSwiftPackageReference "CollectionVGrid" */;
productName = CollectionVGrid;
};
E104DC912B9D89A2008F506D /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
package = E104DC8B2B9D8979008F506D /* XCRemoteSwiftPackageReference "CollectionHStack" */;
productName = CollectionHStack;
};
E104DC932B9D89A2008F506D /* CollectionVGrid */ = {
isa = XCSwiftPackageProductDependency;
package = E104DC8E2B9D8995008F506D /* XCRemoteSwiftPackageReference "CollectionVGrid" */;
@ -4484,6 +4483,21 @@
package = 62666E3727E502CE00EC0ECD /* XCRemoteSwiftPackageReference "SwizzleSwift" */;
productName = SwizzleSwift;
};
E1392FEC2BA218A80034110D /* SwiftUIIntrospect */ = {
isa = XCSwiftPackageProductDependency;
package = 5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
productName = SwiftUIIntrospect;
};
E1392FF12BA21B360034110D /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
package = E1392FF02BA21B360034110D /* XCRemoteSwiftPackageReference "CollectionHStack" */;
productName = CollectionHStack;
};
E1392FF32BA21B470034110D /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
package = E1392FF02BA21B360034110D /* XCRemoteSwiftPackageReference "CollectionHStack" */;
productName = CollectionHStack;
};
E13AF3B528A0C598009093AB /* Nuke */ = {
isa = XCSwiftPackageProductDependency;
package = E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */;
@ -4567,6 +4581,15 @@
isa = XCSwiftPackageProductDependency;
productName = PreferencesView;
};
E15EFA832BA167350080E926 /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
productName = CollectionHStack;
};
E15EFA852BA1685F0080E926 /* SwiftUIIntrospect */ = {
isa = XCSwiftPackageProductDependency;
package = 5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
productName = SwiftUIIntrospect;
};
E18443CA2A037773002DDDC8 /* UDPBroadcast */ = {
isa = XCSwiftPackageProductDependency;
package = E1FAD1C42A0375BA007F5521 /* XCRemoteSwiftPackageReference "UDPBroadcastConnection" */;

View File

@ -15,7 +15,7 @@
"location" : "https://github.com/LePips/CollectionHStack",
"state" : {
"branch" : "main",
"revision" : "ff54c0ea655840a12e7faaabb31aa66f50cc4767"
"revision" : "e192023a2f2ce9351cbe7fb6f01c47043de209a8"
}
},
{

View File

@ -10,6 +10,8 @@ import Defaults
import JellyfinAPI
import SwiftUI
// TODO: image aspect fill/fit
struct PosterButton<Item: Poster>: View {
private var item: Item
@ -20,19 +22,12 @@ struct PosterButton<Item: Poster>: View {
private var onSelect: () -> Void
private var singleImage: Bool
@ViewBuilder
private func poster(from item: Item) -> some View {
private func imageView(from item: Item) -> ImageView {
switch type {
case .portrait:
ImageView(item.portraitPosterImageSource(maxWidth: 200))
.failure {
TypeSystemNameView(item: item)
}
case .landscape:
ImageView(item.landscapePosterImageSources(maxWidth: 500, single: singleImage))
.failure {
TypeSystemNameView(item: item)
}
}
}
@ -44,7 +39,10 @@ struct PosterButton<Item: Poster>: View {
ZStack {
Color.clear
poster(from: item)
imageView(from: item)
.failure {
SystemImageContentView(systemName: item.typeSystemImage)
}
imageOverlay()
.eraseToAnyView()

View File

@ -41,7 +41,7 @@ struct PosterHStack<Item: Poster>: View {
}
.clipsToBounds(false)
.dataPrefix(20)
.horizontalInset(EdgeInsets.defaultEdgePadding)
.insets(horizontal: EdgeInsets.defaultEdgePadding)
.itemSpacing(EdgeInsets.defaultEdgePadding / 2)
.scrollBehavior(.continuousLeadingEdge)
}
@ -64,7 +64,7 @@ struct PosterHStack<Item: Poster>: View {
}
.clipsToBounds(false)
.dataPrefix(20)
.horizontalInset(EdgeInsets.defaultEdgePadding)
.insets(horizontal: EdgeInsets.defaultEdgePadding)
.itemSpacing(EdgeInsets.defaultEdgePadding / 2)
.scrollBehavior(.continuousLeadingEdge)
}

View File

@ -76,7 +76,7 @@ struct SeriesEpisodeSelector: View {
}
}
.scrollBehavior(.continuousLeadingEdge)
.horizontalInset(16)
.insets(horizontal: EdgeInsets.defaultEdgePadding)
.itemSpacing(8)
}
}

View File

@ -21,6 +21,15 @@ extension PagingLibraryView {
private var onSelect: () -> Void
private let posterType: PosterType
private func imageView(from element: Element) -> ImageView {
switch posterType {
case .portrait:
ImageView(element.portraitPosterImageSource(maxWidth: 60))
case .landscape:
ImageView(element.landscapePosterImageSources(maxWidth: 110, single: false))
}
}
@ViewBuilder
private func itemAccessoryView(item: BaseItemDto) -> some View {
DotHStack {
@ -70,17 +79,9 @@ extension PagingLibraryView {
ZStack {
Color.clear
switch posterType {
case .portrait:
ImageView(item.portraitPosterImageSource(maxWidth: 60))
imageView(from: item)
.failure {
TypeSystemNameView(item: item)
}
case .landscape:
ImageView(item.landscapePosterImageSources(maxWidth: 110, single: false))
.failure {
TypeSystemNameView(item: item)
}
SystemImageContentView(systemName: item.typeSystemImage)
}
}
.posterStyle(posterType)

View File

@ -36,11 +36,11 @@ extension VideoPlayer.Overlay {
@EnvironmentObject
private var viewModel: VideoPlayerViewModel
@State
private var scrollViewProxy: ScrollViewProxy? = nil
@StateObject
private var collectionHStackProxy: CollectionHStackProxy<ChapterInfo.FullInfo> = .init()
var body: some View {
VStack {
VStack(spacing: 0) {
Spacer(minLength: 0)
.allowsHitTesting(false)
@ -56,9 +56,8 @@ extension VideoPlayer.Overlay {
Button {
if let currentChapter = viewModel.chapter(from: currentProgressHandler.seconds) {
withAnimation {
scrollViewProxy?.scrollTo(currentChapter.hashValue, anchor: .center)
}
let index = viewModel.chapters.firstIndex(of: currentChapter)!
collectionHStackProxy.scrollTo(index: index)
}
} label: {
Text(L10n.current)
@ -67,20 +66,87 @@ extension VideoPlayer.Overlay {
}
}
.padding(.horizontal, safeAreaInsets.leading)
.if(UIDevice.isPad) { view in
view.padding(.horizontal)
}
.edgePadding(.horizontal)
// ScrollViewReader { proxy in
CollectionHStack(
viewModel.chapters,
minWidth: 200
) { chapter in
PosterButton(
item: chapter,
type: .landscape
ChapterButton(chapter: chapter)
}
.insets(horizontal: EdgeInsets.defaultEdgePadding, vertical: EdgeInsets.defaultEdgePadding)
.proxy(collectionHStackProxy)
.onChange(of: currentOverlayType) { newValue in
guard newValue == .chapters else { return }
if let currentChapter = viewModel.chapter(from: currentProgressHandler.seconds) {
let index = viewModel.chapters.firstIndex(of: currentChapter)!
collectionHStackProxy.scrollTo(index: index, animated: false)
}
}
}
.background {
LinearGradient(
stops: [
.init(color: .clear, location: 0),
.init(color: .black.opacity(0.4), location: 0.4),
.init(color: .black.opacity(0.9), location: 1),
],
startPoint: .top,
endPoint: .bottom
)
.content {
.allowsHitTesting(false)
}
}
}
}
extension VideoPlayer.Overlay.ChapterOverlay {
struct ChapterButton: View {
@Default(.accentColor)
private var accentColor
@EnvironmentObject
private var currentProgressHandler: VideoPlayerManager.CurrentProgressHandler
@EnvironmentObject
private var overlayTimer: TimerProxy
@EnvironmentObject
private var videoPlayerManager: VideoPlayerManager
@EnvironmentObject
private var videoPlayerProxy: VLCVideoPlayer.Proxy
let chapter: ChapterInfo.FullInfo
var body: some View {
Button {
let seconds = chapter.chapterInfo.startTimeSeconds
videoPlayerProxy.setTime(.seconds(seconds))
if videoPlayerManager.state != .playing {
videoPlayerProxy.play()
}
} label: {
VStack(alignment: .leading) {
ZStack {
Color.black
ImageView(chapter.landscapePosterImageSources(maxWidth: 500, single: false))
.failure {
SystemImageContentView(systemName: chapter.typeSystemImage)
}
.aspectRatio(contentMode: .fit)
}
.posterStyle(.landscape)
.overlay {
if chapter.secondsRange.contains(currentProgressHandler.seconds) {
RoundedRectangle(cornerRadius: 1)
.stroke(accentColor, lineWidth: 5)
.transition(.opacity.animation(.linear(duration: 0.1)))
}
}
VStack(alignment: .leading, spacing: 5) {
Text(chapter.chapterInfo.displayTitle)
.font(.subheadline)
@ -100,81 +166,7 @@ extension VideoPlayer.Overlay {
}
}
}
.scrollBehavior(.continuousLeadingEdge)
.horizontalInset(safeAreaInsets.leading)
// ScrollView(.horizontal, showsIndicators: false) {
// HStack(alignment: .top, spacing: 15) {
// ForEach(viewModel.chapters, id: \.self) { chapter in
// PosterButton(
// item: chapter,
// type: .landscape
// )
// .imageOverlay {
// if chapter.secondsRange.contains(currentProgressHandler.seconds) {
// RoundedRectangle(cornerRadius: 6)
// .stroke(accentColor, lineWidth: 8)
// }
// }
// .content {
// VStack(alignment: .leading, spacing: 5) {
// Text(chapter.chapterInfo.displayTitle)
// .font(.subheadline)
// .fontWeight(.semibold)
// .lineLimit(1)
// .foregroundColor(.white)
//
// Text(chapter.chapterInfo.timestampLabel)
// .font(.subheadline)
// .fontWeight(.semibold)
// .foregroundColor(Color(UIColor.systemBlue))
// .padding(.vertical, 2)
// .padding(.horizontal, 4)
// .background {
// Color(UIColor.darkGray).opacity(0.2).cornerRadius(4)
// }
// }
// }
// .onSelect {
// let seconds = chapter.chapterInfo.startTimeSeconds
// videoPlayerProxy.setTime(.seconds(seconds))
//
// if videoPlayerManager.state != .playing {
// videoPlayerProxy.play()
// }
// }
// }
// }
// .padding(.leading, safeAreaInsets.leading)
// .padding(.trailing, safeAreaInsets.trailing)
// .padding(.bottom)
// .if(UIDevice.isPad) { view in
// view.padding(.horizontal)
// }
// }
// .onChange(of: currentOverlayType) { newValue in
// guard newValue == .chapters else { return }
// if let currentChapter = viewModel.chapter(from: currentProgressHandler.seconds) {
// scrollViewProxy?.scrollTo(currentChapter.hashValue, anchor: .center)
// }
// }
// .onAppear {
// scrollViewProxy = proxy
// }
// }
}
.background {
LinearGradient(
stops: [
.init(color: .clear, location: 0),
.init(color: .black.opacity(0.4), location: 0.4),
.init(color: .black.opacity(0.9), location: 1),
],
startPoint: .top,
endPoint: .bottom
)
.allowsHitTesting(false)
}
.buttonStyle(.plain)
}
}
}