From fb38394a43f130f49e4e4ad35c5b2c84cb3a6452 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 14 Sep 2022 20:44:28 -0600 Subject: [PATCH] iOS/iPadOS - User List Images (#586) --- .../Views/SettingsView/SettingsView.swift | 6 - Swiftfin.xcodeproj/project.pbxproj | 4 + .../xcshareddata/swiftpm/Package.resolved | 2 +- Swiftfin/Components/UserProfileButton.swift | 57 +++++++++ Swiftfin/Views/UserListView.swift | 118 +++++++----------- 5 files changed, 110 insertions(+), 77 deletions(-) create mode 100644 Swiftfin/Components/UserProfileButton.swift diff --git a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift index 480d4eec..e506858a 100644 --- a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift +++ b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift @@ -154,9 +154,3 @@ struct SettingsView: View { } } } - -struct SettingsView_Previews: PreviewProvider { - static var previews: some View { - SettingsView(viewModel: SettingsViewModel(server: .sample, user: .sample)) - } -} diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 8531202d..aa6fe438 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -409,6 +409,7 @@ E18E023C288749540022598C /* UIScrollViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E0239288749540022598C /* UIScrollViewExtensions.swift */; }; E19169CE272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; }; E19169CF272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; }; + E192608028D28AAD002314B4 /* UserProfileButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E192607F28D28AAD002314B4 /* UserProfileButton.swift */; }; E1937A3B288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */; }; E1937A3C288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */; }; E1937A3E288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */; }; @@ -893,6 +894,7 @@ E18E0203288749200022598C /* BlurView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurView.swift; sourceTree = ""; }; E18E0239288749540022598C /* UIScrollViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIScrollViewExtensions.swift; sourceTree = ""; }; E19169CD272514760085832A /* HTTPScheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPScheme.swift; sourceTree = ""; }; + E192607F28D28AAD002314B4 /* UserProfileButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileButton.swift; sourceTree = ""; }; E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+Images.swift"; sourceTree = ""; }; E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScreenExtensions.swift; sourceTree = ""; }; E1937A60288F32DB00CB80AA /* Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Poster.swift; sourceTree = ""; }; @@ -1500,6 +1502,7 @@ E1AA331C2782541500F6439C /* PrimaryButton.swift */, E18E01A4288746AF0022598C /* RefreshableScrollView.swift */, E1D3043428D1763100587289 /* SeeAllButton.swift */, + E192607F28D28AAD002314B4 /* UserProfileButton.swift */, ); path = Components; sourceTree = ""; @@ -2920,6 +2923,7 @@ E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */, E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */, 53EE24E6265060780068F029 /* SearchView.swift in Sources */, + E192608028D28AAD002314B4 /* UserProfileButton.swift in Sources */, 625CB5752678C33500530A6E /* MediaViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 251f8fb6..2d005916 100644 --- a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "location" : "https://github.com/LePips/CollectionView", "state" : { "branch" : "main", - "revision" : "1dbf31d860626f8debdbb08201517a4684d226c6" + "revision" : "b05ad718700cc99a4b88009ede6cf04c7326cd99" } }, { diff --git a/Swiftfin/Components/UserProfileButton.swift b/Swiftfin/Components/UserProfileButton.swift new file mode 100644 index 00000000..6f2cae38 --- /dev/null +++ b/Swiftfin/Components/UserProfileButton.swift @@ -0,0 +1,57 @@ +// +// 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) 2022 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI +import SwiftUI + +struct UserProfileButton: View { + + let user: UserDto + private var action: () -> Void + + init(user: UserDto) { + self.user = user + self.action = {} + } + + init(user: SwiftfinStore.State.User) { + self.init(user: .init(name: user.username, id: user.id)) + } + + var body: some View { + VStack(alignment: .center) { + Button { + action() + } label: { + ImageView(user.profileImageSource(maxWidth: 120, maxHeight: 120)) + .failure { + ZStack { + Color.secondarySystemFill + .opacity(0.5) + + Image(systemName: "person.fill") + .resizable() + .frame(width: 60, height: 60) + } + } + .clipShape(Circle()) + } + .frame(width: 120, height: 120) + + Text(user.name ?? .emptyDash) + } + } +} + +extension UserProfileButton { + func onSelect(_ action: @escaping () -> Void) -> Self { + var copy = self + copy.action = action + return copy + } +} diff --git a/Swiftfin/Views/UserListView.swift b/Swiftfin/Views/UserListView.swift index b598f4a1..afcd833b 100644 --- a/Swiftfin/Views/UserListView.swift +++ b/Swiftfin/Views/UserListView.swift @@ -6,6 +6,7 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import CollectionView import SwiftUI struct UserListView: View { @@ -15,44 +16,6 @@ struct UserListView: View { @ObservedObject var viewModel: UserListViewModel - private var listView: some View { - ScrollView { - LazyVStack { - ForEach(viewModel.users, id: \.id) { user in - Button { - viewModel.signIn(user: user) - } label: { - ZStack(alignment: Alignment.leading) { - Rectangle() - .foregroundColor(Color(UIColor.secondarySystemFill)) - .frame(height: 50) - .cornerRadius(10) - - HStack { - Text(user.username) - .font(.title2) - - Spacer() - - if viewModel.isLoading { - ProgressView() - } - }.padding(.leading) - } - .padding() - } - .contextMenu { - Button(role: .destructive) { - viewModel.remove(user: user) - } label: { - Label(L10n.remove, systemImage: "trash") - } - } - } - } - } - } - private var noUserView: some View { VStack { L10n.signInGetStarted.text @@ -68,44 +31,59 @@ struct UserListView: View { } @ViewBuilder - private var innerBody: some View { - if viewModel.users.isEmpty { - noUserView - .offset(y: -50) - } else { - listView - } - } - - @ViewBuilder - private var toolbarContent: some View { - HStack { - Button { - userListRouter.route(to: \.serverDetail, viewModel.server) - } label: { - Image(systemName: "info.circle.fill") - } - - if !viewModel.users.isEmpty { - Button { - userListRouter.route(to: \.userSignIn, viewModel.server) - } label: { - Image(systemName: "person.crop.circle.fill.badge.plus") + private var gridView: some View { + CollectionView(items: viewModel.users) { _, user, _ in + UserProfileButton(user: user) + .onSelect { + viewModel.signIn(user: user) } - } + .contextMenu { + Button(role: .destructive) { + viewModel.remove(user: user) + } label: { + Label(L10n.remove, systemImage: "trash") + } + } + } + .layout { _, layoutEnvironment in + .grid( + layoutEnvironment: layoutEnvironment, + layoutMode: .adaptive(withMinItemSize: 120), + itemSpacing: 30, + lineSpacing: 30 + ) } } var body: some View { - innerBody - .navigationTitle(viewModel.server.name) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - toolbarContent + Group { + if viewModel.users.isEmpty { + noUserView + .offset(y: -50) + } else { + gridView + } + } + .navigationTitle(viewModel.server.name) + .toolbar { + ToolbarItemGroup(placement: .navigationBarTrailing) { + if !viewModel.users.isEmpty { + Button { + userListRouter.route(to: \.userSignIn, viewModel.server) + } label: { + Image(systemName: "person.crop.circle.fill.badge.plus") + } + } + + Button { + userListRouter.route(to: \.serverDetail, viewModel.server) + } label: { + Image(systemName: "info.circle.fill") } } - .onAppear { - viewModel.fetchUsers() - } + } + .onAppear { + viewModel.fetchUsers() + } } }