diff --git a/Shared/Components/SystemImageContentView.swift b/Shared/Components/SystemImageContentView.swift index cd9ff06a..dcf237cc 100644 --- a/Shared/Components/SystemImageContentView.swift +++ b/Shared/Components/SystemImageContentView.swift @@ -60,6 +60,7 @@ struct SystemImageContentView: View { self.title = title } + @ViewBuilder private var imageView: some View { Image(systemName: systemName) .resizable() diff --git a/Shared/Components/TruncatedText.swift b/Shared/Components/TruncatedText.swift index 41dc86e0..a5ba38c5 100644 --- a/Shared/Components/TruncatedText.swift +++ b/Shared/Components/TruncatedText.swift @@ -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) diff --git a/Swiftfin tvOS/Components/PagingLibraryView.swift b/Swiftfin tvOS/Components/PagingLibraryView.swift index c4044925..33191127 100644 --- a/Swiftfin tvOS/Components/PagingLibraryView.swift +++ b/Swiftfin tvOS/Components/PagingLibraryView.swift @@ -144,6 +144,7 @@ struct PagingLibraryView: View { Button(item.displayTitle) } + @ViewBuilder private var contentView: some View { CollectionVGrid( $viewModel.elements, diff --git a/Swiftfin tvOS/Views/ChannelLibraryView/ChannelLibraryView.swift b/Swiftfin tvOS/Views/ChannelLibraryView/ChannelLibraryView.swift index 9c4713a8..f91db188 100644 --- a/Swiftfin tvOS/Views/ChannelLibraryView/ChannelLibraryView.swift +++ b/Swiftfin tvOS/Views/ChannelLibraryView/ChannelLibraryView.swift @@ -19,6 +19,7 @@ struct ChannelLibraryView: View { @StateObject private var viewModel = ChannelLibraryViewModel() + @ViewBuilder private var contentView: some View { CollectionVGrid( $viewModel.elements, diff --git a/Swiftfin tvOS/Views/ChannelLibraryView/Components/WideChannelGridItem.swift b/Swiftfin tvOS/Views/ChannelLibraryView/Components/WideChannelGridItem.swift index 4b473a3f..eb0741e3 100644 --- a/Swiftfin tvOS/Views/ChannelLibraryView/Components/WideChannelGridItem.swift +++ b/Swiftfin tvOS/Views/ChannelLibraryView/Components/WideChannelGridItem.swift @@ -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 { diff --git a/Swiftfin tvOS/Views/ConnectToServerView.swift b/Swiftfin tvOS/Views/ConnectToServerView.swift index 7fe228ad..70744865 100644 --- a/Swiftfin tvOS/Views/ConnectToServerView.swift +++ b/Swiftfin tvOS/Views/ConnectToServerView.swift @@ -100,6 +100,7 @@ struct ConnectToServerView: View { .buttonStyle(.plain) } + @ViewBuilder private var localServersSection: some View { Section(L10n.localServers) { if viewModel.localServers.isEmpty { diff --git a/Swiftfin tvOS/Views/HomeView/HomeView.swift b/Swiftfin tvOS/Views/HomeView/HomeView.swift index 79884581..232932f4 100644 --- a/Swiftfin tvOS/Views/HomeView/HomeView.swift +++ b/Swiftfin tvOS/Views/HomeView/HomeView.swift @@ -19,6 +19,7 @@ struct HomeView: View { @StateObject private var viewModel = HomeViewModel() + @ViewBuilder private var contentView: some View { ScrollView { VStack(alignment: .leading, spacing: 0) { diff --git a/Swiftfin tvOS/Views/MediaView/Components/MediaItem.swift b/Swiftfin tvOS/Views/MediaView/Components/MediaItem.swift index b7ae95a3..fc0d607c 100644 --- a/Swiftfin tvOS/Views/MediaView/Components/MediaItem.swift +++ b/Swiftfin tvOS/Views/MediaView/Components/MediaItem.swift @@ -53,6 +53,7 @@ extension MediaView { } } + @ViewBuilder private var titleLabel: some View { Text(mediaType.displayTitle) .font(.title2) diff --git a/Swiftfin tvOS/Views/MediaView/MediaView.swift b/Swiftfin tvOS/Views/MediaView/MediaView.swift index a43f0925..25743ca4 100644 --- a/Swiftfin tvOS/Views/MediaView/MediaView.swift +++ b/Swiftfin tvOS/Views/MediaView/MediaView.swift @@ -20,6 +20,7 @@ struct MediaView: View { @StateObject private var viewModel = MediaViewModel() + @ViewBuilder private var contentView: some View { CollectionVGrid( $viewModel.mediaItems, diff --git a/Swiftfin tvOS/Views/ProgramsView/ProgramsView.swift b/Swiftfin tvOS/Views/ProgramsView/ProgramsView.swift index 48266531..fc621108 100644 --- a/Swiftfin tvOS/Views/ProgramsView/ProgramsView.swift +++ b/Swiftfin tvOS/Views/ProgramsView/ProgramsView.swift @@ -21,6 +21,7 @@ struct ProgramsView: View { @StateObject private var programsViewModel = ProgramsViewModel() + @ViewBuilder private var contentView: some View { ScrollView(showsIndicators: false) { VStack(spacing: 20) { diff --git a/Swiftfin tvOS/Views/SearchView.swift b/Swiftfin tvOS/Views/SearchView.swift index 5985c3d5..ac64051f 100644 --- a/Swiftfin tvOS/Views/SearchView.swift +++ b/Swiftfin tvOS/Views/SearchView.swift @@ -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) { diff --git a/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift b/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift index 615cfb00..ff961e55 100644 --- a/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift +++ b/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift @@ -48,6 +48,7 @@ extension SelectUserView { return isSelected ? .primary : .secondary } + @ViewBuilder private var personView: some View { ZStack { Color.secondarySystemFill diff --git a/Swiftfin tvOS/Views/UserSignInView/Components/PublicUserRow.swift b/Swiftfin tvOS/Views/UserSignInView/Components/PublicUserRow.swift index 60b03249..7843b621 100644 --- a/Swiftfin tvOS/Views/UserSignInView/Components/PublicUserRow.swift +++ b/Swiftfin tvOS/Views/UserSignInView/Components/PublicUserRow.swift @@ -29,6 +29,7 @@ extension UserSignInView { self.action = action } + @ViewBuilder private var personView: some View { ZStack { Color.secondarySystemFill diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index cb857f1d..f087b788 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -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 = ""; }; E1A42E5028CBE44500A14DCB /* LandscapePosterProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandscapePosterProgressBar.swift; sourceTree = ""; }; E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = ""; }; + E1A8FDEB2C0574A800D0A51C /* ListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRow.swift; sourceTree = ""; }; E1AA331C2782541500F6439C /* PrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = ""; }; E1AA331E2782639D00F6439C /* OverlayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayType.swift; sourceTree = ""; }; E1AD104C26D96CE3003E4A08 /* BaseItemDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemDto.swift; sourceTree = ""; }; @@ -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 */, diff --git a/Swiftfin/Components/ListRow.swift b/Swiftfin/Components/ListRow.swift new file mode 100644 index 00000000..625485f6 --- /dev/null +++ b/Swiftfin/Components/ListRow.swift @@ -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: 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) + } +} diff --git a/Swiftfin/Components/ListRowButton.swift b/Swiftfin/Components/ListRowButton.swift index 3800571e..da110b83 100644 --- a/Swiftfin/Components/ListRowButton.swift +++ b/Swiftfin/Components/ListRowButton.swift @@ -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 diff --git a/Swiftfin/Views/ChannelLibraryView/ChannelLibraryView.swift b/Swiftfin/Views/ChannelLibraryView/ChannelLibraryView.swift index ec3cc6e6..3ff48842 100644 --- a/Swiftfin/Views/ChannelLibraryView/ChannelLibraryView.swift +++ b/Swiftfin/Views/ChannelLibraryView/ChannelLibraryView.swift @@ -95,6 +95,7 @@ struct ChannelLibraryView: View { } } + @ViewBuilder private var contentView: some View { CollectionVGrid( $viewModel.elements, diff --git a/Swiftfin/Views/ChannelLibraryView/Components/DetailedChannelView.swift b/Swiftfin/Views/ChannelLibraryView/Components/DetailedChannelView.swift index 85851440..93c6ed6f 100644 --- a/Swiftfin/Views/ChannelLibraryView/Components/DetailedChannelView.swift +++ b/Swiftfin/Views/ChannelLibraryView/Components/DetailedChannelView.swift @@ -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 { diff --git a/Swiftfin/Views/ConnectToServerView.swift b/Swiftfin/Views/ConnectToServerView.swift index 8d464c60..57eece5f 100644 --- a/Swiftfin/Views/ConnectToServerView.swift +++ b/Swiftfin/Views/ConnectToServerView.swift @@ -93,6 +93,7 @@ struct ConnectToServerView: View { .buttonStyle(.plain) } + @ViewBuilder private var localServersSection: some View { Section(L10n.localServers) { if viewModel.localServers.isEmpty { diff --git a/Swiftfin/Views/HomeView/HomeView.swift b/Swiftfin/Views/HomeView/HomeView.swift index a1743d14..b1868310 100644 --- a/Swiftfin/Views/HomeView/HomeView.swift +++ b/Swiftfin/Views/HomeView/HomeView.swift @@ -29,6 +29,7 @@ struct HomeView: View { @StateObject private var viewModel = HomeViewModel() + @ViewBuilder private var contentView: some View { ScrollView { VStack(alignment: .leading, spacing: 10) { diff --git a/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift b/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift index 22365aae..f7c1f13e 100644 --- a/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift +++ b/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift @@ -97,6 +97,7 @@ extension ItemView { return CGSize(width: width, height: height) } + @ViewBuilder private var imageView: some View { ZStack { Color.clear diff --git a/Swiftfin/Views/MediaView/Components/MediaItem.swift b/Swiftfin/Views/MediaView/Components/MediaItem.swift index c31fe887..a40b80f8 100644 --- a/Swiftfin/Views/MediaView/Components/MediaItem.swift +++ b/Swiftfin/Views/MediaView/Components/MediaItem.swift @@ -62,6 +62,7 @@ extension MediaView { } } + @ViewBuilder private var titleLabel: some View { Text(mediaType.displayTitle) .font(.title2) diff --git a/Swiftfin/Views/MediaView/MediaView.swift b/Swiftfin/Views/MediaView/MediaView.swift index 40ff4d41..7b0a7a01 100644 --- a/Swiftfin/Views/MediaView/MediaView.swift +++ b/Swiftfin/Views/MediaView/MediaView.swift @@ -33,6 +33,7 @@ struct MediaView: View { .columns(2) } + @ViewBuilder private var contentView: some View { CollectionVGrid( $viewModel.mediaItems, diff --git a/Swiftfin/Views/PagingLibraryView/Components/LibraryRow.swift b/Swiftfin/Views/PagingLibraryView/Components/LibraryRow.swift index b4e80529..e24a3782 100644 --- a/Swiftfin/Views/PagingLibraryView/Components/LibraryRow.swift +++ b/Swiftfin/Views/PagingLibraryView/Components/LibraryRow.swift @@ -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) } } diff --git a/Swiftfin/Views/ProgramsView/ProgramsView.swift b/Swiftfin/Views/ProgramsView/ProgramsView.swift index 9e598097..acb86de5 100644 --- a/Swiftfin/Views/ProgramsView/ProgramsView.swift +++ b/Swiftfin/Views/ProgramsView/ProgramsView.swift @@ -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) { diff --git a/Swiftfin/Views/SearchView.swift b/Swiftfin/Views/SearchView.swift index f18ddd5d..c2f97b19 100644 --- a/Swiftfin/Views/SearchView.swift +++ b/Swiftfin/Views/SearchView.swift @@ -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) { diff --git a/Swiftfin/Views/SelectUserView/Components/AddUserButton.swift b/Swiftfin/Views/SelectUserView/Components/AddUserButton.swift index b1b097ae..4b345bed 100644 --- a/Swiftfin/Views/SelectUserView/Components/AddUserButton.swift +++ b/Swiftfin/Views/SelectUserView/Components/AddUserButton.swift @@ -44,6 +44,7 @@ extension SelectUserView { self.servers = servers } + @ViewBuilder private var content: some View { VStack(alignment: .center) { ZStack { diff --git a/Swiftfin/Views/SelectUserView/Components/AddUserRow.swift b/Swiftfin/Views/SelectUserView/Components/AddUserRow.swift index f739386a..89da093a 100644 --- a/Swiftfin/Views/SelectUserView/Components/AddUserRow.swift +++ b/Swiftfin/Views/SelectUserView/Components/AddUserRow.swift @@ -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) } } } diff --git a/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift b/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift index b71801e9..1c0761b5 100644 --- a/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift +++ b/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift @@ -50,6 +50,7 @@ extension SelectUserView { return isSelected ? .primary : .secondary } + @ViewBuilder private var personView: some View { ZStack { Group { diff --git a/Swiftfin/Views/SelectUserView/Components/UserRow.swift b/Swiftfin/Views/SelectUserView/Components/UserRow.swift index 616fbc2c..a272a80c 100644 --- a/Swiftfin/Views/SelectUserView/Components/UserRow.swift +++ b/Swiftfin/Views/SelectUserView/Components/UserRow.swift @@ -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) } } } diff --git a/Swiftfin/Views/SelectUserView/SelectUserView.swift b/Swiftfin/Views/SelectUserView/SelectUserView.swift index 9fa4ea8f..af251b51 100644 --- a/Swiftfin/Views/SelectUserView/SelectUserView.swift +++ b/Swiftfin/Views/SelectUserView/SelectUserView.swift @@ -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 diff --git a/Swiftfin/Views/UserSignInView/Components/PublicUserRow.swift b/Swiftfin/Views/UserSignInView/Components/PublicUserRow.swift index 0b9b3e7e..16960b13 100644 --- a/Swiftfin/Views/UserSignInView/Components/PublicUserRow.swift +++ b/Swiftfin/Views/UserSignInView/Components/PublicUserRow.swift @@ -30,6 +30,7 @@ extension UserSignInView { self.action = action } + @ViewBuilder private var personView: some View { ZStack { Group {