Skip to content

Commit

Permalink
fix(user): work around SwiftUI bug causing profile popover caching is…
Browse files Browse the repository at this point in the history
…sues
  • Loading branch information
cryptoAlgorithm committed Sep 26, 2023
1 parent 6410fd8 commit c41d70e
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ struct MessageView: View, Equatable {
if MessageView.defaultTypes.contains(message.type) {
if !shrunk {
UserAvatarView(user: message.author, guildID: serverCtx.guild!.id, webhookID: message.webhook_id)
.equatable()
} else {
Text(message.timestamp, style: .time)
.font(.system(size: 8, weight: .semibold, design: .monospaced))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct ReferenceMessageView: View {
guildID: serverCtx.guild?.id,
webhookID: quotedMsg.webhook_id,
size: 16
).equatable()
)

Group {
Text(quotedMsg.author.username)
Expand Down
7 changes: 1 addition & 6 deletions Swiftcord/Views/Settings/User/UserSettingsProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ struct UserSettingsProfileView: View {
let user: CurrentUser

@State private var about = ""
@State private var profile: UserProfile?

var body: some View {
Section {
Expand All @@ -33,7 +32,7 @@ struct UserSettingsProfileView: View {
Text("Preview").font(.headline)
MiniUserProfileView(
user: User(from: user),
profile: $profile
member: nil
) {
Text("Customising my profile").font(.headline).textCase(.uppercase)

Expand All @@ -56,10 +55,6 @@ struct UserSettingsProfileView: View {
.cornerRadius(8)
.shadow(color: .black.opacity(0.24), radius: 8, x: 0, y: 4)
.onAppear {
profile = UserProfile(
connected_accounts: [],
user: User(from: user)
)
about = user.bio ?? ""
}
}
Expand Down
59 changes: 34 additions & 25 deletions Swiftcord/Views/User/Avatar/UserAvatarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,37 @@ struct ProfileKey: Hashable {
let userID: Snowflake
}

private let profileCache = Cache<ProfileKey, UserProfile>()

struct UserAvatarView: View, Equatable {
struct UserAvatarView: View {
let user: User
let guildID: Snowflake?
let webhookID: Snowflake?
var size: CGFloat = 40
@State private var profile: UserProfile? // Lazy-loaded full user
@State private var fullUser: User?
@State private var member: Member?
@State private var infoPresenting = false
@State private var loadFullFailed = false
@State private var note = ""

@EnvironmentObject var ctx: ServerContext
@EnvironmentObject var gateway: DiscordGateway

private static let profileCache = Cache<ProfileKey, (User, Member?)>()

var body: some View {
// _ = print("render!")
let avatarURL = user.avatarURL(size: size == 40 ? 160 : Int(size)*2)
// This is actually crucial to resolve a SwiftUI bug preventing a required rerender when this property changes
let _ = member // swiftlint:disable:this redundant_discardable_let

Button {
if user.id == gateway.cache.user?.id, profile == nil {
profile = UserProfile(
connected_accounts: [],
guild_member: nil,
premium_guild_since: nil,
premium_since: nil,
mutual_guilds: nil,
user: User(from: gateway.cache.user!)
)
if user.id == gateway.cache.user?.id, fullUser == nil {
fullUser = User(from: gateway.cache.user!)
}

if let cached = profileCache[ProfileKey(guildID: guildID, userID: user.id)] { profile = cached }
if let (cUser, cMember) = Self.profileCache[ProfileKey(guildID: guildID, userID: user.id)] {
member = cMember
fullUser = cUser
}

infoPresenting.toggle()
AnalyticsWrapper.event(type: .openPopout, properties: [
Expand All @@ -59,14 +58,16 @@ struct UserAvatarView: View, Equatable {
}

// Get user profile for a fuller User object and roles
if profile?.guild_member == nil, webhookID == nil, guildID != "@me" || profile?.user == nil {
if member == nil || fullUser == nil, webhookID == nil, guildID != "@me" {
Task {
do {
profile = try await restAPI.getProfile(
let profile = try await restAPI.getProfile(
user: user.id,
guildID: guildID == "@me" ? nil : guildID
)
profileCache[ProfileKey(guildID: guildID, userID: user.id)] = profile
member = profile.guild_member
fullUser = profile.user
Self.profileCache[ProfileKey(guildID: guildID, userID: user.id)] = (profile.user, profile.guild_member)
} catch {
loadFullFailed = true
}
Expand All @@ -80,20 +81,27 @@ struct UserAvatarView: View, Equatable {
.buttonStyle(.borderless)
.popover(isPresented: $infoPresenting, arrowEdge: .trailing) {
MiniUserProfileView(
user: user,
profile: $profile,
user: fullUser ?? user,
member: member,
guildRoles: ctx.roles,
isWebhook: webhookID != nil,
loadError: loadFullFailed
) {
if let profile = profile, guildID != "@me" {
if member == nil, !loadFullFailed {
ProgressView("Loading full profile...")
.progressViewStyle(.linear)
.frame(maxWidth: .infinity)
.tint(.blue)
}

if guildID != "@me" {
let guildRoles = ctx.roles
let roles = guildRoles.filter {
profile.guild_member?.roles.contains($0.id) ?? false
member?.roles.contains($0.id) ?? false
}

Text(
profile.guild_member == nil
member == nil
? "user.roles.loading"
: (roles.isEmpty ? "user.roles.none" : (roles.count == 1 ? "user.roles.one" : "user.roles.many"))
)
Expand All @@ -119,6 +127,7 @@ struct UserAvatarView: View, Equatable {
).padding(-2)
}
}

Text("user.note")
.font(.headline)
.textCase(.uppercase)
Expand All @@ -140,7 +149,7 @@ struct UserAvatarView: View, Equatable {
}
}

static func == (lhs: UserAvatarView, rhs: UserAvatarView) -> Bool {
lhs.user.id == rhs.user.id
}
/*static func == (lhs: UserAvatarView, rhs: UserAvatarView) -> Bool {
lhs.user.id == rhs.user.id && lhs.profile?.user.id == rhs.profile?.user.id
}*/
}
5 changes: 1 addition & 4 deletions Swiftcord/Views/User/CurrentUserFooter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,7 @@ struct CurrentUserFooter: View {
}
.buttonStyle(.plain)
.popover(isPresented: $userPopoverPresented) {
MiniUserProfileView(user: User(from: user), profile: .constant(UserProfile(
connected_accounts: [],
user: User(from: user)
))) {
MiniUserProfileView(user: User(from: user), member: nil) {
VStack(spacing: 4) {
if !(user.bio?.isEmpty ?? true) { Divider() }

Expand Down
23 changes: 7 additions & 16 deletions Swiftcord/Views/User/Profile/MiniUserProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import CachedAsyncImage
struct MiniUserProfileView<RichContentSlot: View>: View {
let user: User
let pasteboard = NSPasteboard.general
@Binding var profile: UserProfile?
let member: Member?
var guildRoles: [Role]?
var isWebhook: Bool = false
var loadError: Bool = false
Expand All @@ -29,20 +29,20 @@ struct MiniUserProfileView<RichContentSlot: View>: View {
let presence = gateway.presences[user.id]

VStack(alignment: .leading, spacing: 0) {
if let banner = profile?.user.banner ?? user.banner {
if let banner = user.banner {
let url = banner.bannerURL(of: user.id, size: 600)
Group {
if url.isAnimatable {
SwiftyGifView(url: url.modifyingPathExtension("gif"))
} else {
CachedAsyncImage(url: url) { image in
image.resizable().scaledToFill()
} placeholder: { Rectangle().fill(Color(hex: profile?.user.accent_color ?? 0)) }
} placeholder: { Rectangle().fill(Color(hex: user.accent_color ?? 0)) }
}
}
.frame(width: 300, height: 120)
.clipShape(ProfileAccentMask(insetStart: 14, insetWidth: 92))
} else if let accentColor = profile?.user.accent_color {
} else if let accentColor = user.accent_color {
Rectangle().fill(Color(hex: accentColor))
.frame(maxWidth: .infinity, minHeight: 60, maxHeight: 60)
.clipShape(ProfileAccentMask(insetStart: 14, insetWidth: 92))
Expand All @@ -62,10 +62,8 @@ struct MiniUserProfileView<RichContentSlot: View>: View {
)
.padding(6)

if let fullUser = profile?.user {
ProfileBadges(user: fullUser, premiumType: profile?.premium_type)
.frame(minHeight: 40, alignment: .topTrailing)
}
ProfileBadges(user: user, premiumType: user.premium_type)
.frame(minHeight: 40, alignment: .topTrailing)
Spacer()
if loadError {
Image(systemName: "exclamationmark.triangle.fill")
Expand Down Expand Up @@ -122,14 +120,7 @@ struct MiniUserProfileView<RichContentSlot: View>: View {
.buttonStyle(FlatButtonStyle())
.controlSize(.small)
} else {
if profile == nil, !loadError {
ProgressView("Loading full profile...")
.progressViewStyle(.linear)
.frame(maxWidth: .infinity)
.tint(.blue)
}

if let bio = profile?.user.bio, !bio.isEmpty {
if let bio = user.bio, !bio.isEmpty {
Text("user.bio").font(.headline).textCase(.uppercase)
Text(markdown: bio)
.fixedSize(horizontal: false, vertical: true)
Expand Down

0 comments on commit c41d70e

Please sign in to comment.