Files
session-ios/SessionMessagingKit/Utilities/ProfilePictureView+Convenience.swift
Morgan Pretty 55ba2aa8ed Merge remote-tracking branch 'upstream/dev' into feature/libsession-message-wrapping
# Conflicts:
#	Session/Conversations/ConversationVC+Interaction.swift
#	Session/Conversations/ConversationVC.swift
#	Session/Conversations/Settings/ThreadSettingsViewModel.swift
#	Session/Home/GlobalSearch/GlobalSearchViewController.swift
#	Session/Meta/AppDelegate.swift
#	SessionMessagingKit/Open Groups/OpenGroupManager.swift
#	SessionMessagingKit/Sending & Receiving/MessageReceiver.swift
#	SessionMessagingKit/Utilities/MessageWrapper.swift
2025-12-17 13:23:36 +11:00

263 lines
11 KiB
Swift

// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SessionUIKit
import SessionUtilitiesKit
public extension ProfilePictureView {
func update(
publicKey: String,
threadVariant: SessionThread.Variant,
displayPictureUrl: String?,
profile: Profile?,
profileIcon: Info.ProfileIcon = .none,
additionalProfile: Profile? = nil,
additionalProfileIcon: Info.ProfileIcon = .none,
using dependencies: Dependencies
) {
let (info, additionalInfo): (front: Info?, back: Info?) = Info.generateInfoFrom(
size: self.size,
publicKey: publicKey,
threadVariant: threadVariant,
displayPictureUrl: displayPictureUrl,
profile: profile,
profileIcon: profileIcon,
additionalProfile: additionalProfile,
additionalProfileIcon: additionalProfileIcon,
using: dependencies
)
guard let info: Info = info else { return }
update(info, additionalInfo: additionalInfo)
}
}
public extension ProfilePictureView.Info {
static func generateInfoFrom(
size: Size,
publicKey: String,
threadVariant: SessionThread.Variant,
displayPictureUrl: String?,
profile: Profile?,
profileIcon: ProfileIcon = .none,
additionalProfile: Profile? = nil,
additionalProfileIcon: ProfileIcon = .none,
using dependencies: Dependencies
) -> (front: ProfilePictureView.Info?, back: ProfilePictureView.Info?) {
let explicitPath: String? = try? dependencies[singleton: .displayPictureManager].path(
for: displayPictureUrl
)
let explicitPathFileExists: Bool = (explicitPath.map { dependencies[singleton: .fileManager].fileExists(atPath: $0) } ?? false)
switch (explicitPath, explicitPathFileExists, publicKey.isEmpty, threadVariant) {
// TODO: [PRO] Deal with this case later when implement group related Pro features
case (.some(let path), true, _, .legacyGroup), (.some(let path), true, _, .group): fallthrough
case (.some(let path), true, _, .community):
/// If we are given an explicit `displayPictureUrl` then only use that
return (ProfilePictureView.Info(
source: .url(URL(fileURLWithPath: path)),
canAnimate: true,
icon: profileIcon
), nil)
case (.some(let path), true, _, _):
/// If we are given an explicit `displayPictureUrl` then only use that
return (
ProfilePictureView.Info(
source: .url(URL(fileURLWithPath: path)),
canAnimate: ProfilePictureView.canProfileAnimate(profile, using: dependencies),
icon: profileIcon
),
nil
)
case (_, _, _, .community):
return (
ProfilePictureView.Info(
source: {
switch size {
case .navigation, .message: return .image("SessionWhite16", #imageLiteral(resourceName: "SessionWhite16"))
case .list: return .image("SessionWhite24", #imageLiteral(resourceName: "SessionWhite24"))
case .hero, .modal, .expanded: return .image("SessionWhite40", #imageLiteral(resourceName: "SessionWhite40"))
}
}(),
canAnimate: true,
inset: {
let padding: CGFloat
switch size {
case .navigation, .message: padding = 7
case .list: padding = 12
case .hero: padding = 28
case .modal: padding = 24
case .expanded: padding = 50
}
return UIEdgeInsets(
top: padding,
left: padding,
bottom: padding,
right: padding
)
}(),
icon: profileIcon,
forcedBackgroundColor: .theme(.classicDark, color: .borderSeparator)
),
nil
)
case (_, _, true, _): return (nil, nil)
case (_, _, _, .legacyGroup), (_, _, _, .group):
let source: ImageDataManager.DataSource = {
guard
let path: String = try? dependencies[singleton: .displayPictureManager]
.path(for: profile?.displayPictureUrl),
dependencies[singleton: .fileManager].fileExists(atPath: path)
else {
return .placeholderIcon(
seed: (profile?.id ?? publicKey),
text: (profile?.displayName() ?? publicKey),
size: (additionalProfile != nil ?
size.multiImageSize :
size.viewSize
)
)
}
return ImageDataManager.DataSource.url(URL(fileURLWithPath: path))
}()
return (
ProfilePictureView.Info(
source: source,
canAnimate: ProfilePictureView.canProfileAnimate(profile, using: dependencies),
icon: profileIcon
),
additionalProfile
.map { other in
let source: ImageDataManager.DataSource = {
guard
let path: String = try? dependencies[singleton: .displayPictureManager]
.path(for: other.displayPictureUrl),
dependencies[singleton: .fileManager].fileExists(atPath: path)
else {
return .placeholderIcon(
seed: other.id,
text: other.displayName(),
size: size.multiImageSize
)
}
return ImageDataManager.DataSource.url(URL(fileURLWithPath: path))
}()
return ProfilePictureView.Info(
source: source,
canAnimate: ProfilePictureView.canProfileAnimate(other, using: dependencies),
icon: additionalProfileIcon
)
}
.defaulting(
to: ProfilePictureView.Info(
source: .image("ic_user_round_fill", UIImage(named: "ic_user_round_fill")),
canAnimate: false,
renderingMode: .alwaysTemplate,
themeTintColor: .white,
inset: UIEdgeInsets(
top: 4,
left: 4,
bottom: -6,
right: 4
),
icon: additionalProfileIcon
)
)
)
case (_, _, _, .contact):
let source: ImageDataManager.DataSource = {
guard
let path: String = try? dependencies[singleton: .displayPictureManager]
.path(for: profile?.displayPictureUrl),
dependencies[singleton: .fileManager].fileExists(atPath: path)
else {
return .placeholderIcon(
seed: publicKey,
text: (profile?.displayName() ?? publicKey),
size: size.viewSize
)
}
return ImageDataManager.DataSource.url(URL(fileURLWithPath: path))
}()
return (
ProfilePictureView.Info(
source: source,
canAnimate: ProfilePictureView.canProfileAnimate(profile, using: dependencies),
icon: profileIcon),
nil
)
}
}
}
public extension ProfilePictureView {
/// This will made a decision based on the current state of the profile data, it's up to the parent screen to observer changes and trigger
/// a UI refresh to update this state
static func canProfileAnimate(_ profile: Profile?, using dependencies: Dependencies) -> Bool {
guard dependencies[feature: .sessionProEnabled] else { return true }
switch profile {
case .none: return false
case .some(let profile) where profile.id == dependencies[cache: .general].sessionId.hexString:
return dependencies[singleton: .sessionProManager].currentUserIsCurrentlyPro
case .some(let profile):
return dependencies[singleton: .sessionProManager]
.profileFeatures(for: profile)
.contains(.animatedAvatar)
}
}
}
public extension ProfilePictureSwiftUI {
init?(
size: ProfilePictureView.Info.Size,
publicKey: String,
threadVariant: SessionThread.Variant,
displayPictureUrl: String?,
profile: Profile?,
profileIcon: ProfilePictureView.Info.ProfileIcon = .none,
additionalProfile: Profile? = nil,
additionalProfileIcon: ProfilePictureView.Info.ProfileIcon = .none,
using dependencies: Dependencies
) {
let (info, additionalInfo) = ProfilePictureView.Info.generateInfoFrom(
size: size,
publicKey: publicKey,
threadVariant: threadVariant,
displayPictureUrl: displayPictureUrl,
profile: profile,
profileIcon: profileIcon,
additionalProfile: additionalProfile,
additionalProfileIcon: additionalProfileIcon,
using: dependencies
)
switch info {
case .none: return nil
case .some(let info):
self.init(
size: size,
info: info,
additionalInfo: additionalInfo,
dataManager: dependencies[singleton: .imageDataManager]
)
}
}
}