Fix iOS Chapter Overlay (#992)
This commit is contained in:
parent
876ffba417
commit
1bd18ef8b0
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -44,7 +44,7 @@ extension BaseItemDto: Poster {
|
|||
}
|
||||
}
|
||||
|
||||
var typeSystemName: String? {
|
||||
var typeSystemImage: String? {
|
||||
switch type {
|
||||
case .episode, .movie, .series:
|
||||
"film"
|
||||
|
|
|
@ -21,7 +21,7 @@ extension BaseItemPerson: Poster {
|
|||
true
|
||||
}
|
||||
|
||||
var typeSystemName: String? {
|
||||
var typeSystemImage: String? {
|
||||
"person.fill"
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" */;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"location" : "https://github.com/LePips/CollectionHStack",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "ff54c0ea655840a12e7faaabb31aa66f50cc4767"
|
||||
"revision" : "e192023a2f2ce9351cbe7fb6f01c47043de209a8"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ struct SeriesEpisodeSelector: View {
|
|||
}
|
||||
}
|
||||
.scrollBehavior(.continuousLeadingEdge)
|
||||
.horizontalInset(16)
|
||||
.insets(horizontal: EdgeInsets.defaultEdgePadding)
|
||||
.itemSpacing(8)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue