[iOS & tvOS] Fix Square Overlay Over Profiles (#1466)

* Fix Square over Circle

* cleanup

---------

Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
Joe Kribs 2025-04-06 15:21:21 -06:00 committed by GitHub
parent 0de40a5788
commit 26ec19982e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 100 additions and 133 deletions

View File

@ -6,18 +6,19 @@
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors // Copyright (c) 2025 Jellyfin & Jellyfin Contributors
// //
import Defaults
import Factory
import JellyfinAPI
import Nuke import Nuke
import SwiftUI import SwiftUI
struct UserProfileImage<Placeholder: View>: View { struct UserProfileImage<Placeholder: View>: View {
// MARK: - Inject Logger // MARK: - Environment Variables
@Injected(\.logService) @Environment(\.isEnabled)
private var logger private var isEnabled
@Environment(\.isEditing)
private var isEditing
@Environment(\.isSelected)
private var isSelected
// MARK: - User Variables // MARK: - User Variables
@ -26,6 +27,17 @@ struct UserProfileImage<Placeholder: View>: View {
private let pipeline: ImagePipeline private let pipeline: ImagePipeline
private let placeholder: Placeholder 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 // MARK: - Body
var body: some View { var body: some View {
@ -46,9 +58,12 @@ struct UserProfileImage<Placeholder: View>: View {
.failure { .failure {
placeholder placeholder
} }
.posterShadow() .overlay {
Color.black
.opacity(overlayOpacity)
}
.aspectRatio(1, contentMode: .fill) .aspectRatio(1, contentMode: .fill)
.clipShape(Circle()) .clipShape(.circle)
.shadow(radius: 5) .shadow(radius: 5)
} }
} }

View File

@ -52,9 +52,10 @@ extension SelectUserView {
return isSelected ? .primary : .secondary return isSelected ? .primary : .secondary
} }
// MARK: - User Portrait // MARK: - Body
private var userPortrait: some View { var body: some View {
Button(action: action) {
UserProfileImage( UserProfileImage(
userID: user.id, userID: user.id,
source: user.profileImageSource( source: user.profileImageSource(
@ -63,35 +64,16 @@ extension SelectUserView {
), ),
pipeline: .Swiftfin.local pipeline: .Swiftfin.local
) )
.aspectRatio(1, contentMode: .fill) .overlay(alignment: .bottom) {
.clipShape(.circle) if isEditing && isSelected {
.overlay {
if isEditing {
ZStack(alignment: .bottom) {
Color.black
.opacity(isSelected ? 0 : 0.5)
if isSelected {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.resizable() .resizable()
.aspectRatio(contentMode: .fill) .aspectRatio(contentMode: .fit)
.frame(width: 75, height: 75) .frame(width: 75, height: 75)
.symbolRenderingMode(.palette) .symbolRenderingMode(.palette)
.foregroundStyle(accentColor.overlayColor, accentColor) .foregroundStyle(accentColor.overlayColor, accentColor)
} }
} }
}
}
}
// MARK: - Body
var body: some View {
VStack {
Button {
action()
} label: {
userPortrait
.hoverEffect(.highlight) .hoverEffect(.highlight)
Text(user.username) Text(user.username)
@ -109,9 +91,12 @@ extension SelectUserView {
.buttonStyle(.borderless) .buttonStyle(.borderless)
.buttonBorderShape(.circle) .buttonBorderShape(.circle)
.contextMenu { .contextMenu {
Button(L10n.delete, role: .destructive) { if !isEditing {
onDelete() Button(
} L10n.delete,
role: .destructive,
action: onDelete
)
} }
} }
} }

View File

@ -82,12 +82,9 @@ extension ServerUsersView {
maxWidth: 60 maxWidth: 60
) )
) )
.grayscale(userActive ? 0.0 : 1.0) .environment(\.isEnabled, userActive)
.environment(\.isEditing, isEditing)
if isEditing { .environment(\.isSelected, isSelected)
Color.black
.opacity(isSelected ? 0 : 0.5)
}
} }
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
} }

View File

@ -17,8 +17,6 @@ extension SelectUserView {
@Default(.accentColor) @Default(.accentColor)
private var accentColor private var accentColor
@Environment(\.colorScheme)
private var colorScheme
@Environment(\.isEditing) @Environment(\.isEditing)
private var isEditing private var isEditing
@Environment(\.isSelected) @Environment(\.isSelected)
@ -51,13 +49,8 @@ extension SelectUserView {
} }
var body: some View { var body: some View {
Button { Button(action: action) {
action() VStack {
} label: {
VStack(alignment: .center) {
ZStack {
Color.clear
UserProfileImage( UserProfileImage(
userID: user.id, userID: user.id,
source: user.profileImageSource( source: user.profileImageSource(
@ -66,17 +59,8 @@ extension SelectUserView {
), ),
pipeline: .Swiftfin.local pipeline: .Swiftfin.local
) )
} .overlay(alignment: .bottomTrailing) {
.aspectRatio(1, contentMode: .fill) if isEditing, isSelected {
.clipShape(.circle)
.overlay {
if isEditing {
ZStack(alignment: .bottomTrailing) {
Color.black
.opacity(isSelected ? 0 : 0.5)
.clipShape(.circle)
if isSelected {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
@ -85,8 +69,6 @@ extension SelectUserView {
.foregroundStyle(accentColor.overlayColor, accentColor) .foregroundStyle(accentColor.overlayColor, accentColor)
} }
} }
}
}
Text(user.username) Text(user.username)
.font(.title3) .font(.title3)
@ -103,8 +85,12 @@ extension SelectUserView {
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.contextMenu { .contextMenu {
Button(L10n.delete, role: .destructive) { if !isEditing {
onDelete() Button(
L10n.delete,
role: .destructive,
action: onDelete
)
} }
} }
} }

View File

@ -69,29 +69,6 @@ extension SelectUserView {
.aspectRatio(1, contentMode: .fill) .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 @ViewBuilder
private var rowContent: some View { private var rowContent: some View {
HStack { HStack {
@ -119,7 +96,14 @@ extension SelectUserView {
var body: some View { var body: some View {
ListRow(insets: .init(horizontal: EdgeInsets.edgePadding)) { ListRow(insets: .init(horizontal: EdgeInsets.edgePadding)) {
userImage UserProfileImage(
userID: user.id,
source: user.profileImageSource(
client: server.client,
maxWidth: 120
),
pipeline: .Swiftfin.local
)
.frame(width: 80) .frame(width: 80)
.padding(.vertical, 8) .padding(.vertical, 8)
} content: { } content: {