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