diff --git a/Shared/Components/UserProfileImage/UserProfileImage.swift b/Shared/Components/UserProfileImage/UserProfileImage.swift index eeabb7e7..8c9a5aa2 100644 --- a/Shared/Components/UserProfileImage/UserProfileImage.swift +++ b/Shared/Components/UserProfileImage/UserProfileImage.swift @@ -6,18 +6,19 @@ // Copyright (c) 2025 Jellyfin & Jellyfin Contributors // -import Defaults -import Factory -import JellyfinAPI import Nuke import SwiftUI struct UserProfileImage: View { - // MARK: - Inject Logger + // MARK: - Environment Variables - @Injected(\.logService) - private var logger + @Environment(\.isEnabled) + private var isEnabled + @Environment(\.isEditing) + private var isEditing + @Environment(\.isSelected) + private var isSelected // MARK: - User Variables @@ -26,6 +27,17 @@ struct UserProfileImage: View { private let pipeline: ImagePipeline private let placeholder: Placeholder + // MARK: - Overlay Opacity + + private var overlayOpacity: Double { + /// Dim the Profile Image if Editing & Unselected or if Disabled + if (isEditing && !isSelected) || !isEnabled { + return 0.5 + } else { + return 0.0 + } + } + // MARK: - Body var body: some View { @@ -46,9 +58,12 @@ struct UserProfileImage: View { .failure { placeholder } - .posterShadow() + .overlay { + Color.black + .opacity(overlayOpacity) + } .aspectRatio(1, contentMode: .fill) - .clipShape(Circle()) + .clipShape(.circle) .shadow(radius: 5) } } diff --git a/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift b/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift index 8301ce71..0dad4647 100644 --- a/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift +++ b/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift @@ -52,66 +52,51 @@ extension SelectUserView { return isSelected ? .primary : .secondary } - // MARK: - User Portrait - - private var userPortrait: some View { - UserProfileImage( - userID: user.id, - source: user.profileImageSource( - client: server.client, - maxWidth: 120 - ), - pipeline: .Swiftfin.local - ) - .aspectRatio(1, contentMode: .fill) - .clipShape(.circle) - .overlay { - if isEditing { - ZStack(alignment: .bottom) { - Color.black - .opacity(isSelected ? 0 : 0.5) - - if isSelected { - Image(systemName: "checkmark.circle.fill") - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 75, height: 75) - .symbolRenderingMode(.palette) - .foregroundStyle(accentColor.overlayColor, accentColor) - } - } - } - } - } - // MARK: - Body var body: some View { - VStack { - Button { - action() - } label: { - userPortrait - .hoverEffect(.highlight) - - Text(user.username) - .font(.title3) - .fontWeight(.semibold) - .foregroundStyle(labelForegroundStyle) - .lineLimit(1) - - if showServer { - Text(server.name) - .font(.footnote) - .foregroundStyle(.secondary) + Button(action: action) { + UserProfileImage( + userID: user.id, + source: user.profileImageSource( + client: server.client, + maxWidth: 120 + ), + pipeline: .Swiftfin.local + ) + .overlay(alignment: .bottom) { + if isEditing && isSelected { + Image(systemName: "checkmark.circle.fill") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 75, height: 75) + .symbolRenderingMode(.palette) + .foregroundStyle(accentColor.overlayColor, accentColor) } } - .buttonStyle(.borderless) - .buttonBorderShape(.circle) - .contextMenu { - Button(L10n.delete, role: .destructive) { - onDelete() - } + .hoverEffect(.highlight) + + Text(user.username) + .font(.title3) + .fontWeight(.semibold) + .foregroundStyle(labelForegroundStyle) + .lineLimit(1) + + if showServer { + Text(server.name) + .font(.footnote) + .foregroundStyle(.secondary) + } + } + .buttonStyle(.borderless) + .buttonBorderShape(.circle) + .contextMenu { + if !isEditing { + Button( + L10n.delete, + role: .destructive, + action: onDelete + ) } } } diff --git a/Swiftfin/Views/AdminDashboardView/ServerUsers/ServerUsersView/Components/ServerUsersRow.swift b/Swiftfin/Views/AdminDashboardView/ServerUsers/ServerUsersView/Components/ServerUsersRow.swift index 1381ee69..f94bc0bc 100644 --- a/Swiftfin/Views/AdminDashboardView/ServerUsers/ServerUsersView/Components/ServerUsersRow.swift +++ b/Swiftfin/Views/AdminDashboardView/ServerUsers/ServerUsersView/Components/ServerUsersRow.swift @@ -82,12 +82,9 @@ extension ServerUsersView { maxWidth: 60 ) ) - .grayscale(userActive ? 0.0 : 1.0) - - if isEditing { - Color.black - .opacity(isSelected ? 0 : 0.5) - } + .environment(\.isEnabled, userActive) + .environment(\.isEditing, isEditing) + .environment(\.isSelected, isSelected) } .frame(width: 60, height: 60) } diff --git a/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift b/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift index 55266be0..e67f9959 100644 --- a/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift +++ b/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift @@ -17,8 +17,6 @@ extension SelectUserView { @Default(.accentColor) private var accentColor - @Environment(\.colorScheme) - private var colorScheme @Environment(\.isEditing) private var isEditing @Environment(\.isSelected) @@ -51,40 +49,24 @@ extension SelectUserView { } var body: some View { - Button { - action() - } label: { - VStack(alignment: .center) { - ZStack { - Color.clear - - UserProfileImage( - userID: user.id, - source: user.profileImageSource( - client: server.client, - maxWidth: 120 - ), - pipeline: .Swiftfin.local - ) - } - .aspectRatio(1, contentMode: .fill) - .clipShape(.circle) - .overlay { - if isEditing { - ZStack(alignment: .bottomTrailing) { - Color.black - .opacity(isSelected ? 0 : 0.5) - .clipShape(.circle) - - if isSelected { - Image(systemName: "checkmark.circle.fill") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 40, height: 40, alignment: .bottomTrailing) - .symbolRenderingMode(.palette) - .foregroundStyle(accentColor.overlayColor, accentColor) - } - } + Button(action: action) { + VStack { + UserProfileImage( + userID: user.id, + source: user.profileImageSource( + client: server.client, + maxWidth: 120 + ), + pipeline: .Swiftfin.local + ) + .overlay(alignment: .bottomTrailing) { + if isEditing, isSelected { + Image(systemName: "checkmark.circle.fill") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 40, height: 40, alignment: .bottomTrailing) + .symbolRenderingMode(.palette) + .foregroundStyle(accentColor.overlayColor, accentColor) } } @@ -103,8 +85,12 @@ extension SelectUserView { } .buttonStyle(.plain) .contextMenu { - Button(L10n.delete, role: .destructive) { - onDelete() + if !isEditing { + Button( + L10n.delete, + role: .destructive, + action: onDelete + ) } } } diff --git a/Swiftfin/Views/SelectUserView/Components/UserRow.swift b/Swiftfin/Views/SelectUserView/Components/UserRow.swift index c5bc65d9..bbc5e7f8 100644 --- a/Swiftfin/Views/SelectUserView/Components/UserRow.swift +++ b/Swiftfin/Views/SelectUserView/Components/UserRow.swift @@ -69,29 +69,6 @@ extension SelectUserView { .aspectRatio(1, contentMode: .fill) } - @ViewBuilder - private var userImage: some View { - ZStack { - Color.clear - - UserProfileImage( - userID: user.id, - source: user.profileImageSource( - client: server.client, - maxWidth: 120 - ), - pipeline: .Swiftfin.local - ) - - if isEditing { - Color.black - .opacity(isSelected ? 0 : 0.5) - } - } - .aspectRatio(contentMode: .fill) - .clipShape(.circle) - } - @ViewBuilder private var rowContent: some View { HStack { @@ -119,9 +96,16 @@ extension SelectUserView { var body: some View { ListRow(insets: .init(horizontal: EdgeInsets.edgePadding)) { - userImage - .frame(width: 80) - .padding(.vertical, 8) + UserProfileImage( + userID: user.id, + source: user.profileImageSource( + client: server.client, + maxWidth: 120 + ), + pipeline: .Swiftfin.local + ) + .frame(width: 80) + .padding(.vertical, 8) } content: { rowContent }