[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,66 +52,51 @@ extension SelectUserView {
return isSelected ? .primary : .secondary 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 // MARK: - Body
var body: some View { var body: some View {
VStack { Button(action: action) {
Button { UserProfileImage(
action() userID: user.id,
} label: { source: user.profileImageSource(
userPortrait client: server.client,
.hoverEffect(.highlight) maxWidth: 120
),
Text(user.username) pipeline: .Swiftfin.local
.font(.title3) )
.fontWeight(.semibold) .overlay(alignment: .bottom) {
.foregroundStyle(labelForegroundStyle) if isEditing && isSelected {
.lineLimit(1) Image(systemName: "checkmark.circle.fill")
.resizable()
if showServer { .aspectRatio(contentMode: .fit)
Text(server.name) .frame(width: 75, height: 75)
.font(.footnote) .symbolRenderingMode(.palette)
.foregroundStyle(.secondary) .foregroundStyle(accentColor.overlayColor, accentColor)
} }
} }
.buttonStyle(.borderless) .hoverEffect(.highlight)
.buttonBorderShape(.circle)
.contextMenu { Text(user.username)
Button(L10n.delete, role: .destructive) { .font(.title3)
onDelete() .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
)
} }
} }
} }

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,40 +49,24 @@ extension SelectUserView {
} }
var body: some View { var body: some View {
Button { Button(action: action) {
action() VStack {
} label: { UserProfileImage(
VStack(alignment: .center) { userID: user.id,
ZStack { source: user.profileImageSource(
Color.clear client: server.client,
maxWidth: 120
UserProfileImage( ),
userID: user.id, pipeline: .Swiftfin.local
source: user.profileImageSource( )
client: server.client, .overlay(alignment: .bottomTrailing) {
maxWidth: 120 if isEditing, isSelected {
), Image(systemName: "checkmark.circle.fill")
pipeline: .Swiftfin.local .resizable()
) .aspectRatio(contentMode: .fit)
} .frame(width: 40, height: 40, alignment: .bottomTrailing)
.aspectRatio(1, contentMode: .fill) .symbolRenderingMode(.palette)
.clipShape(.circle) .foregroundStyle(accentColor.overlayColor, accentColor)
.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)
}
}
} }
} }
@ -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,9 +96,16 @@ extension SelectUserView {
var body: some View { var body: some View {
ListRow(insets: .init(horizontal: EdgeInsets.edgePadding)) { ListRow(insets: .init(horizontal: EdgeInsets.edgePadding)) {
userImage UserProfileImage(
.frame(width: 80) userID: user.id,
.padding(.vertical, 8) source: user.profileImageSource(
client: server.client,
maxWidth: 120
),
pipeline: .Swiftfin.local
)
.frame(width: 80)
.padding(.vertical, 8)
} content: { } content: {
rowContent rowContent
} }