Skip to content

Commit 9e45ef3

Browse files
committed
wip
1 parent 6df8928 commit 9e45ef3

79 files changed

Lines changed: 793 additions & 2044 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,24 @@
88

99
import SwiftUI
1010

11+
#if os(iOS)
12+
private let defaultPadding: CGFloat = 5
13+
private let defaultCircleSize: CGFloat = 2
14+
#else
15+
private let defaultPadding: CGFloat = 10
16+
private let defaultCircleSize: CGFloat = 5
17+
#endif
18+
1119
func DotHStack(
12-
padding: CGFloat = 5,
20+
padding: CGFloat = defaultPadding,
1321
@ViewBuilder content: @escaping () -> some View
1422
) -> some View {
1523
SeparatorHStack {
1624
Circle()
17-
.frame(width: 2, height: 2)
25+
.frame(
26+
width: defaultCircleSize,
27+
height: defaultCircleSize
28+
)
1829
.padding(.horizontal, padding)
1930
} content: {
2031
content()

Swiftfin/Views/PagingLibraryView/Components/LibraryRow.swift renamed to Shared/Components/LibraryRow.swift

File renamed without changes.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ struct ListRow<Leading: View, Content: View>: View {
5151
}
5252
.foregroundStyle(.primary, .secondary)
5353
.contentShape(.contextMenuPreview, Rectangle())
54-
.listRowSeparator(.hidden)
54+
// .listRowSeparator(.hidden)
5555
.overlay(alignment: .bottomTrailing) {
5656
Color.secondarySystemFill
5757
.frame(

Shared/Components/MarkedList.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99
import SwiftUI
1010

1111
/// A `VStack` that displays subviews with a marker on the top leading edge.
12-
///
13-
/// In a marker view, ensure that views that are only used for layout are
14-
/// tagged with `hidden` to avoid them being read by accessibility features.
1512
struct MarkedList<Content: View, Marker: View>: View {
1613

1714
private let content: Content
Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import SwiftUI
1212

1313
struct PosterButton<Item: Poster, Label: View>: View {
1414

15-
@ForTypeInEnvironment<Item, (Any) -> PosterStyleEnvironment>(\.posterStyleRegistry)
15+
@ForTypeInEnvironment<Item, AnyForPosterStyleEnvironment>(\.posterStyleRegistry)
1616
private var posterStyleRegistry
1717

1818
@Namespace
@@ -43,52 +43,69 @@ struct PosterButton<Item: Poster, Label: View>: View {
4343
}
4444

4545
@ViewBuilder
46-
private func posterView(overlay: some View = EmptyView()) -> some View {
47-
VStack(alignment: .leading) {
48-
PosterImage(
49-
item: item,
50-
type: posterStyle.displayType
51-
)
52-
.frame(maxWidth: .infinity, maxHeight: .infinity)
53-
.overlay { overlay }
54-
.contentShape(.contextMenuPreview, Rectangle())
55-
.posterCornerRadius(posterStyle.displayType)
56-
.backport
57-
.matchedTransitionSource(id: "item", in: namespace)
58-
.posterShadow()
59-
60-
Group {
61-
if Label.self != EmptyView.self {
62-
label
63-
} else {
64-
posterStyle.label
65-
}
46+
private func posterImage(overlay: some View = EmptyView()) -> some View {
47+
PosterImage(
48+
item: item,
49+
type: posterStyle.displayType
50+
)
51+
.frame(maxWidth: .infinity, maxHeight: .infinity)
52+
.overlay { overlay }
53+
.contentShape(.contextMenuPreview, Rectangle())
54+
.posterCornerRadius(posterStyle.displayType)
55+
.backport
56+
.matchedTransitionSource(id: "item", in: namespace)
57+
.posterShadow()
58+
.hoverEffect(.highlight)
59+
}
60+
61+
@ViewBuilder
62+
private var resolvedLabel: some View {
63+
Group {
64+
if Label.self != EmptyView.self {
65+
label
66+
} else {
67+
posterStyle.label
6668
}
67-
.allowsHitTesting(false)
69+
}
70+
.allowsHitTesting(false)
71+
}
72+
73+
@ViewBuilder
74+
private func buttonLabel(overlay: some View = EmptyView()) -> some View {
75+
VStack(alignment: .leading) {
76+
posterImage(overlay: overlay)
77+
resolvedLabel
6878
}
6979
}
7080

7181
var body: some View {
7282
Button {
7383
action(namespace)
7484
} label: {
75-
posterView(overlay: posterStyle.overlay(posterStyle.displayType))
85+
// For focused offset label behavior on tvOS, this layout is required
86+
#if os(tvOS)
87+
posterImage(overlay: posterStyle.overlay(posterStyle.displayType))
88+
resolvedLabel
89+
.frame(maxWidth: .infinity, alignment: .leading)
90+
#else
91+
buttonLabel(overlay: posterStyle.overlay(posterStyle.displayType))
7692
.trackingSize($posterSize)
93+
#endif
7794
}
7895
.foregroundStyle(.primary, .secondary)
79-
.buttonStyle(.plain)
96+
.buttonStyle(.borderless)
8097
.matchedContextMenu(for: item) {
8198
let frameScale = 1.3
8299

83-
posterView()
100+
buttonLabel()
84101
.frame(
85102
width: posterSize.width * frameScale,
86103
height: posterSize.height * frameScale
87104
)
88105
.padding(20)
89106
.background {
90107
RoundedRectangle(cornerRadius: 10)
91-
.fill(Color(uiColor: UIColor.secondarySystemGroupedBackground))
108+
.fill(.complexSecondary)
92109
}
93110
}
94111
}
Lines changed: 67 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,41 @@
77
//
88

99
import CollectionHStack
10-
import Defaults
11-
import JellyfinAPI
1210
import SwiftUI
1311

1412
// TODO: check accessibility
1513

1614
struct PosterHStack<
1715
Element: Poster,
1816
Data: Collection,
19-
Header: View,
20-
Label: View
17+
Header: View
2118
>: View where Data.Element == Element, Data.Index == Int {
2219

23-
@ForTypeInEnvironment<Element, (Any) -> PosterStyleEnvironment>(\.posterStyleRegistry)
20+
@ForTypeInEnvironment<Element, AnyForPosterStyleEnvironment>(\.posterStyleRegistry)
2421
private var posterStyleRegistry
2522

2623
@Router
2724
private var router
2825

29-
private var data: Data
26+
private var elements: Data
3027
private var header: Header
31-
private var title: String
3228
// TODO: remove?
3329
private var type: PosterDisplayType
34-
private var trailingContent: () -> any View
3530
private var action: (Element, Namespace.ID) -> Void
3631

3732
private var posterStyle: PosterStyleEnvironment {
38-
guard let first = data.first else { return .default }
33+
guard let first = elements.first else { return .default }
3934
return posterStyleRegistry?(first) ?? .default
4035
}
4136

4237
private var layout: CollectionHStackLayout {
43-
// if UIDevice.isPhone {
44-
// return .grid(
45-
// columns: type == .landscape ? 2 : 3,
46-
// rows: 1,
47-
// columnTrailingInset: 0
48-
// )
49-
// } else {
50-
// return .minimumWidth(
51-
// columnWidth: type == .landscape ? 220 : 140,
52-
// rows: 1
53-
// )
54-
// }
55-
38+
#if os(tvOS)
39+
.grid(
40+
columns: posterStyle.displayType == .landscape ? 4 : 7,
41+
rows: 1,
42+
columnTrailingInset: 0
43+
)
44+
#else
5645
let columnCount: CGFloat = {
5746
switch (posterStyle.displayType, posterStyle.size) {
5847
case (.landscape, .small):
@@ -71,40 +60,58 @@ struct PosterHStack<
7160
rows: 1,
7261
columnTrailingInset: 0
7362
)
63+
#endif
64+
65+
// if UIDevice.isPhone {
66+
// return .grid(
67+
// columns: type == .landscape ? 2 : 3,
68+
// rows: 1,
69+
// columnTrailingInset: 0
70+
// )
71+
// } else {
72+
// return .minimumWidth(
73+
// columnWidth: type == .landscape ? 220 : 140,
74+
// rows: 1
75+
// )
76+
// }
77+
}
78+
79+
private var itemSpacing: CGFloat {
80+
#if os(tvOS)
81+
EdgeInsets.edgePadding
82+
#else
83+
EdgeInsets.edgePadding / 2
84+
#endif
7485
}
7586

7687
@ViewBuilder
7788
private var stack: some View {
78-
CollectionHStack(
79-
uniqueElements: data,
80-
layout: layout
81-
) { item in
82-
PosterButton(
83-
item: item,
84-
type: type
85-
) { namespace in
86-
action(item, namespace)
89+
if elements.isNotEmpty {
90+
CollectionHStack(
91+
uniqueElements: elements,
92+
layout: layout
93+
) { item in
94+
PosterButton(
95+
item: item,
96+
type: type
97+
) { namespace in
98+
action(item, namespace)
99+
}
87100
}
101+
.clipsToBounds(false)
102+
.dataPrefix(20)
103+
.insets(horizontal: EdgeInsets.edgePadding)
104+
.itemSpacing(itemSpacing)
105+
.scrollBehavior(.continuousLeadingEdge)
88106
}
89-
.clipsToBounds(false)
90-
.dataPrefix(20)
91-
.insets(horizontal: EdgeInsets.edgePadding)
92-
.itemSpacing(EdgeInsets.edgePadding / 2)
93-
.scrollBehavior(.continuousLeadingEdge)
94107
}
95108

96109
var body: some View {
97-
VStack(alignment: .leading) {
98-
99-
HStack {
100-
header
110+
let _ = Self._printChanges()
101111

102-
Spacer()
112+
VStack(alignment: .leading) {
103113

104-
trailingContent()
105-
.eraseToAnyView()
106-
}
107-
.edgePadding(.horizontal)
114+
header
108115

109116
stack
110117
}
@@ -113,31 +120,20 @@ struct PosterHStack<
113120

114121
extension PosterHStack {
115122

116-
func trailing(@ViewBuilder _ content: @escaping () -> any View) -> Self {
117-
copy(modifying: \.trailingContent, with: content)
118-
}
119-
}
120-
121-
extension PosterHStack where Header == DefaultHeader {
122-
123123
init(
124-
title: String,
124+
elements: Data,
125125
type: PosterDisplayType,
126-
items: Data,
127-
action: @escaping (Element, Namespace.ID) -> Void
126+
action: @escaping (Element, Namespace.ID) -> Void,
127+
@ViewBuilder header: () -> Header
128128
) {
129-
self.init(
130-
data: items,
131-
header: DefaultHeader(title: title),
132-
title: title,
133-
type: type,
134-
trailingContent: { EmptyView() },
135-
action: action
136-
)
129+
self.elements = elements
130+
self.header = header()
131+
self.type = type
132+
self.action = action
137133
}
138134
}
139135

140-
extension PosterHStack where Header == DefaultHeader, Label == TitleSubtitleContentView {
136+
extension PosterHStack where Header == DefaultHeader {
141137

142138
init(
143139
title: String,
@@ -146,11 +142,9 @@ extension PosterHStack where Header == DefaultHeader, Label == TitleSubtitleCont
146142
action: @escaping (Element, Namespace.ID) -> Void
147143
) {
148144
self.init(
149-
data: items,
145+
elements: items,
150146
header: DefaultHeader(title: title),
151-
title: title,
152147
type: type,
153-
trailingContent: { EmptyView() },
154148
action: action
155149
)
156150
}
@@ -160,14 +154,13 @@ extension PosterHStack where Header == DefaultHeader, Label == TitleSubtitleCont
160154

161155
struct DefaultHeader: View {
162156

163-
let title: String?
157+
let title: String
164158

165159
var body: some View {
166-
if let title {
167-
Text(title)
168-
.font(.title2)
169-
.fontWeight(.semibold)
170-
.accessibility(addTraits: [.isHeader])
171-
}
160+
Text(title)
161+
.font(.title2)
162+
.fontWeight(.semibold)
163+
.accessibilityAddTraits(.isHeader)
164+
.edgePadding(.horizontal)
172165
}
173166
}

Shared/Components/PosterImage.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,25 @@ import SwiftUI
1212

1313
/// Retrieving images by exact pixel dimensions is a bit
1414
/// intense for normal usage and eases cache usage and modifications.
15+
#if os(iOS)
1516
private let landscapeMaxWidth: CGFloat = 300
1617
private let portraitMaxWidth: CGFloat = 200
18+
#else
19+
private let landscapeMaxWidth: CGFloat = 500
20+
private let portraitMaxWidth: CGFloat = 500
21+
#endif
1722

1823
struct PosterImage<Element: Poster>: View {
1924

20-
@ForTypeInEnvironment<Element, (Any) -> PosterStyleEnvironment>(\.posterStyleRegistry)
25+
@ForTypeInEnvironment<Element, AnyForPosterStyleEnvironment>(\.posterStyleRegistry)
2126
private var posterStyleRegistry
2227

2328
@ForTypeInEnvironment<Element, (Any) -> any CustomEnvironmentValue>(\.customEnvironmentValueRegistry)
2429
private var customEnvironmentValueRegistry
2530

2631
private let contentMode: ContentMode
2732
private let element: Element
33+
// TODO: figure out what to do with this
2834
private let imageMaxWidth: CGFloat
2935
private var pipeline: ImagePipeline
3036
private let type: PosterDisplayType

0 commit comments

Comments
 (0)