Cleanup (#1077)
This commit is contained in:
parent
fd4052ed53
commit
257091ba9a
|
@ -60,6 +60,7 @@ struct SystemImageContentView: View {
|
||||||
self.title = title
|
self.title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var imageView: some View {
|
private var imageView: some View {
|
||||||
Image(systemName: systemName)
|
Image(systemName: systemName)
|
||||||
.resizable()
|
.resizable()
|
||||||
|
|
|
@ -35,6 +35,7 @@ struct TruncatedText: View {
|
||||||
private var seeMoreType: SeeMoreType
|
private var seeMoreType: SeeMoreType
|
||||||
private let text: String
|
private let text: String
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var textView: some View {
|
private var textView: some View {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
Text(text)
|
Text(text)
|
||||||
|
|
|
@ -144,6 +144,7 @@ struct PagingLibraryView<Element: Poster>: View {
|
||||||
Button(item.displayTitle)
|
Button(item.displayTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
CollectionVGrid(
|
CollectionVGrid(
|
||||||
$viewModel.elements,
|
$viewModel.elements,
|
||||||
|
|
|
@ -19,6 +19,7 @@ struct ChannelLibraryView: View {
|
||||||
@StateObject
|
@StateObject
|
||||||
private var viewModel = ChannelLibraryViewModel()
|
private var viewModel = ChannelLibraryViewModel()
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
CollectionVGrid(
|
CollectionVGrid(
|
||||||
$viewModel.elements,
|
$viewModel.elements,
|
||||||
|
|
|
@ -25,6 +25,7 @@ extension ChannelLibraryView {
|
||||||
private var onSelect: () -> Void
|
private var onSelect: () -> Void
|
||||||
private let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
|
private let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var channelLogo: some View {
|
private var channelLogo: some View {
|
||||||
VStack {
|
VStack {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|
|
@ -100,6 +100,7 @@ struct ConnectToServerView: View {
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var localServersSection: some View {
|
private var localServersSection: some View {
|
||||||
Section(L10n.localServers) {
|
Section(L10n.localServers) {
|
||||||
if viewModel.localServers.isEmpty {
|
if viewModel.localServers.isEmpty {
|
||||||
|
|
|
@ -19,6 +19,7 @@ struct HomeView: View {
|
||||||
@StateObject
|
@StateObject
|
||||||
private var viewModel = HomeViewModel()
|
private var viewModel = HomeViewModel()
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
|
|
@ -53,6 +53,7 @@ extension MediaView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var titleLabel: some View {
|
private var titleLabel: some View {
|
||||||
Text(mediaType.displayTitle)
|
Text(mediaType.displayTitle)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
|
|
|
@ -20,6 +20,7 @@ struct MediaView: View {
|
||||||
@StateObject
|
@StateObject
|
||||||
private var viewModel = MediaViewModel()
|
private var viewModel = MediaViewModel()
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
CollectionVGrid(
|
CollectionVGrid(
|
||||||
$viewModel.mediaItems,
|
$viewModel.mediaItems,
|
||||||
|
|
|
@ -21,6 +21,7 @@ struct ProgramsView: View {
|
||||||
@StateObject
|
@StateObject
|
||||||
private var programsViewModel = ProgramsViewModel()
|
private var programsViewModel = ProgramsViewModel()
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView(showsIndicators: false) {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ struct SearchView: View {
|
||||||
@State
|
@State
|
||||||
private var searchQuery = ""
|
private var searchQuery = ""
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var suggestionsView: some View {
|
private var suggestionsView: some View {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
ForEach(viewModel.suggestions) { item in
|
ForEach(viewModel.suggestions) { item in
|
||||||
|
@ -38,6 +39,7 @@ struct SearchView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var resultsView: some View {
|
private var resultsView: some View {
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView(showsIndicators: false) {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
|
|
|
@ -48,6 +48,7 @@ extension SelectUserView {
|
||||||
return isSelected ? .primary : .secondary
|
return isSelected ? .primary : .secondary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var personView: some View {
|
private var personView: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.secondarySystemFill
|
Color.secondarySystemFill
|
||||||
|
|
|
@ -29,6 +29,7 @@ extension UserSignInView {
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var personView: some View {
|
private var personView: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.secondarySystemFill
|
Color.secondarySystemFill
|
||||||
|
|
|
@ -702,6 +702,7 @@
|
||||||
E1A7B1662B9ADAD300152546 /* ItemTypeLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */; };
|
E1A7B1662B9ADAD300152546 /* ItemTypeLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */; };
|
||||||
E1A7F0DF2BD4EC7400620DDD /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */; };
|
E1A7F0DF2BD4EC7400620DDD /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */; };
|
||||||
E1A7F0E02BD4EC7400620DDD /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */; };
|
E1A7F0E02BD4EC7400620DDD /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */; };
|
||||||
|
E1A8FDEC2C0574A800D0A51C /* ListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A8FDEB2C0574A800D0A51C /* ListRow.swift */; };
|
||||||
E1AA331D2782541500F6439C /* PrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AA331C2782541500F6439C /* PrimaryButton.swift */; };
|
E1AA331D2782541500F6439C /* PrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AA331C2782541500F6439C /* PrimaryButton.swift */; };
|
||||||
E1AA331F2782639D00F6439C /* OverlayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AA331E2782639D00F6439C /* OverlayType.swift */; };
|
E1AA331F2782639D00F6439C /* OverlayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AA331E2782639D00F6439C /* OverlayType.swift */; };
|
||||||
E1AD104D26D96CE3003E4A08 /* BaseItemDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD104C26D96CE3003E4A08 /* BaseItemDto.swift */; };
|
E1AD104D26D96CE3003E4A08 /* BaseItemDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD104C26D96CE3003E4A08 /* BaseItemDto.swift */; };
|
||||||
|
@ -1378,6 +1379,7 @@
|
||||||
E1A42E4E28CBD3E100A14DCB /* HomeErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeErrorView.swift; sourceTree = "<group>"; };
|
E1A42E4E28CBD3E100A14DCB /* HomeErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeErrorView.swift; sourceTree = "<group>"; };
|
||||||
E1A42E5028CBE44500A14DCB /* LandscapePosterProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandscapePosterProgressBar.swift; sourceTree = "<group>"; };
|
E1A42E5028CBE44500A14DCB /* LandscapePosterProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandscapePosterProgressBar.swift; sourceTree = "<group>"; };
|
||||||
E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = "<group>"; };
|
E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = "<group>"; };
|
||||||
|
E1A8FDEB2C0574A800D0A51C /* ListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRow.swift; sourceTree = "<group>"; };
|
||||||
E1AA331C2782541500F6439C /* PrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = "<group>"; };
|
E1AA331C2782541500F6439C /* PrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = "<group>"; };
|
||||||
E1AA331E2782639D00F6439C /* OverlayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayType.swift; sourceTree = "<group>"; };
|
E1AA331E2782639D00F6439C /* OverlayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayType.swift; sourceTree = "<group>"; };
|
||||||
E1AD104C26D96CE3003E4A08 /* BaseItemDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemDto.swift; sourceTree = "<group>"; };
|
E1AD104C26D96CE3003E4A08 /* BaseItemDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemDto.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2073,6 +2075,7 @@
|
||||||
E178B0752BE435D70023651B /* HourMinutePicker.swift */,
|
E178B0752BE435D70023651B /* HourMinutePicker.swift */,
|
||||||
E1FE69A928C29CC20021BC93 /* LandscapePosterProgressBar.swift */,
|
E1FE69A928C29CC20021BC93 /* LandscapePosterProgressBar.swift */,
|
||||||
4E16FD4E2C0183B500110147 /* LetterPickerBar */,
|
4E16FD4E2C0183B500110147 /* LetterPickerBar */,
|
||||||
|
E1A8FDEB2C0574A800D0A51C /* ListRow.swift */,
|
||||||
E1AEFA362BE317E200CFAFD8 /* ListRowButton.swift */,
|
E1AEFA362BE317E200CFAFD8 /* ListRowButton.swift */,
|
||||||
E1FE69AF28C2DA4A0021BC93 /* NavigationBarFilterDrawer */,
|
E1FE69AF28C2DA4A0021BC93 /* NavigationBarFilterDrawer */,
|
||||||
E1DE84132B9531C1008CCE21 /* OrderedSectionSelectorView.swift */,
|
E1DE84132B9531C1008CCE21 /* OrderedSectionSelectorView.swift */,
|
||||||
|
@ -4361,6 +4364,7 @@
|
||||||
E1BAFE102BE921270069C4D7 /* SwiftfinApp+ValueObservation.swift in Sources */,
|
E1BAFE102BE921270069C4D7 /* SwiftfinApp+ValueObservation.swift in Sources */,
|
||||||
62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
||||||
E10B1E8F2BD7728400A92EAF /* QuickConnectView.swift in Sources */,
|
E10B1E8F2BD7728400A92EAF /* QuickConnectView.swift in Sources */,
|
||||||
|
E1A8FDEC2C0574A800D0A51C /* ListRow.swift in Sources */,
|
||||||
E1DD55372B6EE533007501C0 /* Task.swift in Sources */,
|
E1DD55372B6EE533007501C0 /* Task.swift in Sources */,
|
||||||
E1194F4E2BEABA9100888DB6 /* NavigationBarCloseButton.swift in Sources */,
|
E1194F4E2BEABA9100888DB6 /* NavigationBarCloseButton.swift in Sources */,
|
||||||
E113133428BE988200930F75 /* NavigationBarFilterDrawer.swift in Sources */,
|
E113133428BE988200930F75 /* NavigationBarFilterDrawer.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
//
|
||||||
|
// 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 (c) 2024 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// TODO: come up with better name along with `ListRowButton`
|
||||||
|
|
||||||
|
// Meant to be used when making a custom list without `List` or `Form`
|
||||||
|
struct ListRow<Leading: View, Content: View>: View {
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var contentSize: CGSize = .zero
|
||||||
|
|
||||||
|
private let leading: () -> Leading
|
||||||
|
private let content: () -> Content
|
||||||
|
private var action: () -> Void
|
||||||
|
private var insets: EdgeInsets
|
||||||
|
private var isSeparatorVisible: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack(alignment: .bottomTrailing) {
|
||||||
|
|
||||||
|
Button {
|
||||||
|
action()
|
||||||
|
} label: {
|
||||||
|
HStack(alignment: .center, spacing: EdgeInsets.edgePadding) {
|
||||||
|
|
||||||
|
leading()
|
||||||
|
|
||||||
|
content()
|
||||||
|
.frame(maxHeight: .infinity)
|
||||||
|
.trackingSize($contentSize)
|
||||||
|
}
|
||||||
|
.padding(.top, insets.top)
|
||||||
|
.padding(.bottom, insets.bottom)
|
||||||
|
.padding(.leading, insets.leading)
|
||||||
|
.padding(.trailing, insets.trailing)
|
||||||
|
}
|
||||||
|
.foregroundStyle(.primary, .secondary)
|
||||||
|
|
||||||
|
Color.secondarySystemFill
|
||||||
|
.frame(width: contentSize.width, height: 1)
|
||||||
|
.padding(.trailing, insets.trailing)
|
||||||
|
.visible(isSeparatorVisible)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ListRow {
|
||||||
|
|
||||||
|
init(
|
||||||
|
insets: EdgeInsets = .zero,
|
||||||
|
@ViewBuilder leading: @escaping () -> Leading,
|
||||||
|
@ViewBuilder content: @escaping () -> Content
|
||||||
|
) {
|
||||||
|
self.init(
|
||||||
|
leading: leading,
|
||||||
|
content: content,
|
||||||
|
action: {},
|
||||||
|
insets: insets,
|
||||||
|
isSeparatorVisible: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSeparatorVisible(_ isVisible: Bool) -> Self {
|
||||||
|
copy(modifying: \.isSeparatorVisible, with: isVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
func onSelect(perform action: @escaping () -> Void) -> Self {
|
||||||
|
copy(modifying: \.action, with: action)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
// TODO: come up with better name along with `ListRow`
|
||||||
|
|
||||||
|
// Meant to be used within `List` or `Form`
|
||||||
struct ListRowButton: View {
|
struct ListRowButton: View {
|
||||||
|
|
||||||
let title: String
|
let title: String
|
||||||
|
|
|
@ -95,6 +95,7 @@ struct ChannelLibraryView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
CollectionVGrid(
|
CollectionVGrid(
|
||||||
$viewModel.elements,
|
$viewModel.elements,
|
||||||
|
|
|
@ -32,6 +32,7 @@ extension ChannelLibraryView {
|
||||||
private var onSelect: () -> Void
|
private var onSelect: () -> Void
|
||||||
private let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
|
private let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var channelLogo: some View {
|
private var channelLogo: some View {
|
||||||
VStack {
|
VStack {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|
|
@ -93,6 +93,7 @@ struct ConnectToServerView: View {
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var localServersSection: some View {
|
private var localServersSection: some View {
|
||||||
Section(L10n.localServers) {
|
Section(L10n.localServers) {
|
||||||
if viewModel.localServers.isEmpty {
|
if viewModel.localServers.isEmpty {
|
||||||
|
|
|
@ -29,6 +29,7 @@ struct HomeView: View {
|
||||||
@StateObject
|
@StateObject
|
||||||
private var viewModel = HomeViewModel()
|
private var viewModel = HomeViewModel()
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
|
|
@ -97,6 +97,7 @@ extension ItemView {
|
||||||
return CGSize(width: width, height: height)
|
return CGSize(width: width, height: height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var imageView: some View {
|
private var imageView: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.clear
|
Color.clear
|
||||||
|
|
|
@ -62,6 +62,7 @@ extension MediaView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var titleLabel: some View {
|
private var titleLabel: some View {
|
||||||
Text(mediaType.displayTitle)
|
Text(mediaType.displayTitle)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
|
|
|
@ -33,6 +33,7 @@ struct MediaView: View {
|
||||||
.columns(2)
|
.columns(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
CollectionVGrid(
|
CollectionVGrid(
|
||||||
$viewModel.mediaItems,
|
$viewModel.mediaItems,
|
||||||
|
|
|
@ -18,7 +18,7 @@ extension PagingLibraryView {
|
||||||
private var contentWidth: CGFloat = 0
|
private var contentWidth: CGFloat = 0
|
||||||
|
|
||||||
private let item: Element
|
private let item: Element
|
||||||
private var onSelect: () -> Void
|
private var action: () -> Void
|
||||||
private let posterType: PosterDisplayType
|
private let posterType: PosterDisplayType
|
||||||
|
|
||||||
private func imageView(from element: Element) -> ImageView {
|
private func imageView(from element: Element) -> ImageView {
|
||||||
|
@ -68,54 +68,51 @@ extension PagingLibraryView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var rowContent: some View {
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text(item.displayTitle)
|
||||||
|
.font(posterType == .landscape ? .subheadline : .callout)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.lineLimit(2)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
|
||||||
|
accessoryView
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(Color(UIColor.lightGray))
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var rowLeading: some View {
|
||||||
|
ZStack {
|
||||||
|
Color.clear
|
||||||
|
|
||||||
|
imageView(from: item)
|
||||||
|
.failure {
|
||||||
|
SystemImageContentView(systemName: item.systemImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.posterStyle(posterType)
|
||||||
|
.frame(width: posterType == .landscape ? 110 : 60)
|
||||||
|
.posterShadow()
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: body
|
// MARK: body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ListRow(insets: .init(horizontal: EdgeInsets.edgePadding)) {
|
||||||
Button {
|
rowLeading
|
||||||
onSelect()
|
} content: {
|
||||||
} label: {
|
rowContent
|
||||||
HStack(alignment: .center, spacing: EdgeInsets.edgePadding) {
|
|
||||||
ZStack {
|
|
||||||
Color.clear
|
|
||||||
|
|
||||||
imageView(from: item)
|
|
||||||
.failure {
|
|
||||||
SystemImageContentView(systemName: item.systemImage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.posterStyle(posterType)
|
|
||||||
.frame(width: posterType == .landscape ? 110 : 60)
|
|
||||||
.posterShadow()
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text(item.displayTitle)
|
|
||||||
.font(posterType == .landscape ? .subheadline : .callout)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
.lineLimit(2)
|
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
|
|
||||||
accessoryView
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(Color(UIColor.lightGray))
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.onSizeChanged { newSize in
|
|
||||||
contentWidth = newSize.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Color.secondarySystemFill
|
|
||||||
.frame(width: contentWidth, height: 1)
|
|
||||||
}
|
}
|
||||||
.edgePadding(.horizontal)
|
.onSelect(perform: action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,12 +122,12 @@ extension PagingLibraryView.LibraryRow {
|
||||||
init(item: Element, posterType: PosterDisplayType) {
|
init(item: Element, posterType: PosterDisplayType) {
|
||||||
self.init(
|
self.init(
|
||||||
item: item,
|
item: item,
|
||||||
onSelect: {},
|
action: {},
|
||||||
posterType: posterType
|
posterType: posterType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func onSelect(_ action: @escaping () -> Void) -> Self {
|
func onSelect(perform action: @escaping () -> Void) -> Self {
|
||||||
copy(modifying: \.onSelect, with: action)
|
copy(modifying: \.action, with: action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ struct ProgramsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var liveTVSectionScrollView: some View {
|
private var liveTVSectionScrollView: some View {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack {
|
HStack {
|
||||||
|
@ -47,6 +48,7 @@ struct ProgramsView: View {
|
||||||
|
|
||||||
// TODO: probably make own pill view
|
// TODO: probably make own pill view
|
||||||
// - see if could merge with item view pills
|
// - see if could merge with item view pills
|
||||||
|
@ViewBuilder
|
||||||
private func liveTVSectionPill(title: String, systemImage: String, onSelect: @escaping () -> Void) -> some View {
|
private func liveTVSectionPill(title: String, systemImage: String, onSelect: @escaping () -> Void) -> some View {
|
||||||
Button {
|
Button {
|
||||||
onSelect()
|
onSelect()
|
||||||
|
@ -62,6 +64,7 @@ struct ProgramsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView(showsIndicators: false) {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ struct SearchView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var suggestionsView: some View {
|
private var suggestionsView: some View {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
ForEach(viewModel.suggestions) { item in
|
ForEach(viewModel.suggestions) { item in
|
||||||
|
@ -50,6 +51,7 @@ struct SearchView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var resultsView: some View {
|
private var resultsView: some View {
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView(showsIndicators: false) {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
|
|
|
@ -44,6 +44,7 @@ extension SelectUserView {
|
||||||
self.servers = servers
|
self.servers = servers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var content: some View {
|
private var content: some View {
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|
|
@ -44,39 +44,54 @@ extension SelectUserView {
|
||||||
self.servers = servers
|
self.servers = servers
|
||||||
}
|
}
|
||||||
|
|
||||||
private var content: some View {
|
@ViewBuilder
|
||||||
HStack(alignment: .center, spacing: EdgeInsets.edgePadding) {
|
private var rowContent: some View {
|
||||||
|
HStack {
|
||||||
|
|
||||||
ZStack {
|
Text("Add User")
|
||||||
Group {
|
.font(.title3)
|
||||||
if colorScheme == .light {
|
.fontWeight(.semibold)
|
||||||
Color.secondarySystemFill
|
.foregroundStyle(isEnabled ? .primary : .secondary)
|
||||||
} else {
|
.lineLimit(2)
|
||||||
Color.tertiarySystemBackground
|
.multilineTextAlignment(.leading)
|
||||||
}
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var rowLeading: some View {
|
||||||
|
ZStack {
|
||||||
|
Group {
|
||||||
|
if colorScheme == .light {
|
||||||
|
Color.secondarySystemFill
|
||||||
|
} else {
|
||||||
|
Color.tertiarySystemBackground
|
||||||
}
|
}
|
||||||
.posterShadow()
|
|
||||||
|
|
||||||
RelativeSystemImageView(systemName: "plus")
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
}
|
||||||
.aspectRatio(1, contentMode: .fill)
|
.posterShadow()
|
||||||
.clipShape(.circle)
|
|
||||||
.frame(width: 80)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
|
|
||||||
HStack {
|
RelativeSystemImageView(systemName: "plus")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.aspectRatio(1, contentMode: .fill)
|
||||||
|
.clipShape(.circle)
|
||||||
|
.frame(width: 80)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
}
|
||||||
|
|
||||||
Text("Add User")
|
@ViewBuilder
|
||||||
.font(.title3)
|
private var content: some View {
|
||||||
.fontWeight(.semibold)
|
ListRow(insets: .init(horizontal: EdgeInsets.edgePadding)) {
|
||||||
.foregroundStyle(isEnabled ? .primary : .secondary)
|
rowLeading
|
||||||
.lineLimit(2)
|
} content: {
|
||||||
.multilineTextAlignment(.leading)
|
rowContent
|
||||||
|
}
|
||||||
Spacer()
|
.isSeparatorVisible(false)
|
||||||
|
.onSelect {
|
||||||
|
if let selectedServer {
|
||||||
|
action(selectedServer)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,15 +115,9 @@ extension SelectUserView {
|
||||||
.disabled(!isEnabled)
|
.disabled(!isEnabled)
|
||||||
.foregroundStyle(.primary, .secondary)
|
.foregroundStyle(.primary, .secondary)
|
||||||
} else {
|
} else {
|
||||||
Button {
|
content
|
||||||
if let selectedServer {
|
.disabled(!isEnabled)
|
||||||
action(selectedServer)
|
.foregroundStyle(.primary, .secondary)
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
.disabled(!isEnabled)
|
|
||||||
.foregroundStyle(.primary, .secondary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ extension SelectUserView {
|
||||||
return isSelected ? .primary : .secondary
|
return isSelected ? .primary : .secondary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var personView: some View {
|
private var personView: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Group {
|
Group {
|
||||||
|
|
|
@ -24,9 +24,6 @@ extension SelectUserView {
|
||||||
@Environment(\.isSelected)
|
@Environment(\.isSelected)
|
||||||
private var isSelected
|
private var isSelected
|
||||||
|
|
||||||
@State
|
|
||||||
private var contentSize: CGSize = .zero
|
|
||||||
|
|
||||||
private let user: UserState
|
private let user: UserState
|
||||||
private let server: ServerState
|
private let server: ServerState
|
||||||
private let showServer: Bool
|
private let showServer: Bool
|
||||||
|
@ -53,6 +50,7 @@ extension SelectUserView {
|
||||||
return isSelected ? .primary : .secondary
|
return isSelected ? .primary : .secondary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var personView: some View {
|
private var personView: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Group {
|
Group {
|
||||||
|
@ -98,73 +96,62 @@ extension SelectUserView {
|
||||||
.clipShape(.circle)
|
.clipShape(.circle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var rowContent: some View {
|
||||||
|
HStack {
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text(user.username)
|
||||||
|
.font(.title3)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundStyle(labelForegroundStyle)
|
||||||
|
.lineLimit(2)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
|
||||||
|
if showServer {
|
||||||
|
Text(server.name)
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(Color(UIColor.lightGray))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if isEditing, isSelected {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.resizable()
|
||||||
|
.backport
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.aspectRatio(1, contentMode: .fit)
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
.symbolRenderingMode(.palette)
|
||||||
|
.foregroundStyle(accentColor.overlayColor, accentColor)
|
||||||
|
|
||||||
|
} else if isEditing {
|
||||||
|
Image(systemName: "circle")
|
||||||
|
.resizable()
|
||||||
|
.backport
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.aspectRatio(1, contentMode: .fit)
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ListRow(insets: .init(horizontal: EdgeInsets.edgePadding)) {
|
||||||
Button {
|
userImage
|
||||||
action()
|
.frame(width: 80)
|
||||||
} label: {
|
.padding(.vertical, 8)
|
||||||
ZStack {
|
} content: {
|
||||||
Color.clear
|
rowContent
|
||||||
|
}
|
||||||
HStack(alignment: .center, spacing: EdgeInsets.edgePadding) {
|
.onSelect(perform: action)
|
||||||
|
.contextMenu {
|
||||||
userImage
|
Button("Delete", role: .destructive) {
|
||||||
.frame(width: 80)
|
onDelete()
|
||||||
.padding(.vertical, 8)
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text(user.username)
|
|
||||||
.font(.title3)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.foregroundStyle(labelForegroundStyle)
|
|
||||||
.lineLimit(2)
|
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
|
|
||||||
if showServer {
|
|
||||||
Text(server.name)
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(Color(UIColor.lightGray))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
if isEditing, isSelected {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.resizable()
|
|
||||||
.backport
|
|
||||||
.fontWeight(.bold)
|
|
||||||
.aspectRatio(1, contentMode: .fit)
|
|
||||||
.frame(width: 24, height: 24)
|
|
||||||
.symbolRenderingMode(.palette)
|
|
||||||
.foregroundStyle(accentColor.overlayColor, accentColor)
|
|
||||||
|
|
||||||
} else if isEditing {
|
|
||||||
Image(systemName: "circle")
|
|
||||||
.resizable()
|
|
||||||
.backport
|
|
||||||
.fontWeight(.bold)
|
|
||||||
.aspectRatio(1, contentMode: .fit)
|
|
||||||
.frame(width: 24, height: 24)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.trackingSize($contentSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.contextMenu {
|
|
||||||
Button("Delete", role: .destructive) {
|
|
||||||
onDelete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.foregroundStyle(.primary, .secondary)
|
|
||||||
|
|
||||||
Color.secondarySystemFill
|
|
||||||
.frame(width: contentSize.width, height: 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -317,7 +317,6 @@ struct SelectUserView: View {
|
||||||
listItemView(for: item)
|
listItemView(for: item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.edgePadding()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,6 +352,7 @@ struct SelectUserView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var deleteUsersButton: some View {
|
private var deleteUsersButton: some View {
|
||||||
Button {
|
Button {
|
||||||
isPresentingConfirmDeleteUsers = true
|
isPresentingConfirmDeleteUsers = true
|
||||||
|
@ -451,6 +451,7 @@ struct SelectUserView: View {
|
||||||
|
|
||||||
// MARK: emptyView
|
// MARK: emptyView
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var emptyView: some View {
|
private var emptyView: some View {
|
||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
L10n.connectToJellyfinServerStart.text
|
L10n.connectToJellyfinServerStart.text
|
||||||
|
|
|
@ -30,6 +30,7 @@ extension UserSignInView {
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var personView: some View {
|
private var personView: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Group {
|
Group {
|
||||||
|
|
Loading…
Reference in New Issue