Updated the profile picture modal and standardised the ProfilePictureView sizes

Fixed an issue where 'CurrentAppContext().isMainAppAndActive' wasn't called on the main thread
Updated the ProfilePictureView to have the updated icon UI
This commit is contained in:
Morgan Pretty 2023-05-24 13:33:53 +10:00
parent 2035d508d9
commit 2d792e4e3e
20 changed files with 344 additions and 144 deletions

View File

@ -205,9 +205,9 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
return
}
guard CurrentAppContext().isMainAppAndActive else { return }
DispatchQueue.main.async {
guard CurrentAppContext().isMainAppAndActive else { return }
guard let presentingVC = CurrentAppContext().frontmostViewController() else {
preconditionFailure() // FIXME: Handle more gracefully
}

View File

@ -4,6 +4,7 @@ import UIKit
import WebRTC
import SessionUIKit
import SessionMessagingKit
import SignalUtilitiesKit
final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate {
private static let swipeToOperateThreshold: CGFloat = 60
@ -20,14 +21,7 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate {
return result
}()
private lazy var profilePictureView: ProfilePictureView = {
let result = ProfilePictureView()
let size: CGFloat = 60
result.size = size
result.set(.width, to: size)
result.set(.height, to: size)
return result
}()
private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .list)
private lazy var displayNameLabel: UILabel = {
let result = UILabel()

View File

@ -1104,15 +1104,13 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
switch threadData.threadVariant {
case .contact:
let profilePictureView = ProfilePictureView()
profilePictureView.size = Values.verySmallProfilePictureSize
let profilePictureView = ProfilePictureView(size: .navigation)
profilePictureView.update(
publicKey: threadData.threadId, // Contact thread uses the contactId
profile: threadData.profile,
threadVariant: threadData.threadVariant
)
profilePictureView.set(.width, to: (44 - 16)) // Width of the standard back button
profilePictureView.set(.height, to: Values.verySmallProfilePictureSize)
profilePictureView.customWidth = (44 - 16) // Width of the standard back button
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
profilePictureView.addGestureRecognizer(tapGestureRecognizer)

View File

@ -4,6 +4,7 @@ import UIKit
import SessionUIKit
import SessionMessagingKit
import SessionUtilitiesKit
import SignalUtilitiesKit
final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, MentionSelectionViewDelegate {
// MARK: - Variables
@ -491,7 +492,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M
func showMentionsUI(for candidates: [MentionInfo]) {
mentionsView.candidates = candidates
let mentionCellHeight = (Values.smallProfilePictureSize + 2 * Values.smallSpacing)
let mentionCellHeight = (ProfilePictureView.Size.message.viewSize + 2 * Values.smallSpacing)
mentionsViewHeightConstraint.constant = CGFloat(min(3, candidates.count)) * mentionCellHeight
layoutIfNeeded()

View File

@ -111,9 +111,7 @@ private extension MentionSelectionView {
final class Cell: UITableViewCell {
// MARK: - UI
private lazy var profilePictureView: ProfilePictureView = ProfilePictureView()
private lazy var moderatorIconImageView: UIImageView = UIImageView(image: #imageLiteral(resourceName: "Crown"))
private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .message)
private lazy var displayNameLabel: UILabel = {
let result: UILabel = UILabel()
@ -155,18 +153,12 @@ private extension MentionSelectionView {
selectedBackgroundView.themeBackgroundColor = .highlighted(.settings_tabBackground)
self.selectedBackgroundView = selectedBackgroundView
// Profile picture image view
let profilePictureViewSize = Values.smallProfilePictureSize
profilePictureView.set(.width, to: profilePictureViewSize)
profilePictureView.set(.height, to: profilePictureViewSize)
profilePictureView.size = profilePictureViewSize
// Main stack view
let mainStackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameLabel ])
mainStackView.axis = .horizontal
mainStackView.alignment = .center
mainStackView.spacing = Values.mediumSpacing
mainStackView.set(.height, to: profilePictureViewSize)
mainStackView.set(.height, to: ProfilePictureView.Size.message.viewSize)
contentView.addSubview(mainStackView)
mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.mediumSpacing)
mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.smallSpacing)
@ -174,13 +166,6 @@ private extension MentionSelectionView {
contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.smallSpacing)
mainStackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing)
// Moderator icon image view
moderatorIconImageView.set(.width, to: 20)
moderatorIconImageView.set(.height, to: 20)
contentView.addSubview(moderatorIconImageView)
moderatorIconImageView.pin(.trailing, to: .trailing, of: profilePictureView, withInset: 1)
moderatorIconImageView.pin(.bottom, to: .bottom, of: profilePictureView, withInset: 4.5)
// Separator
addSubview(separator)
separator.pin(.leading, to: .leading, of: self)
@ -200,9 +185,9 @@ private extension MentionSelectionView {
profilePictureView.update(
publicKey: profile.id,
profile: profile,
icon: (isUserModeratorOrAdmin ? .crown : .none),
threadVariant: threadVariant
)
moderatorIconImageView.isHidden = !isUserModeratorOrAdmin
separator.isHidden = isLast
}
}

View File

@ -22,7 +22,6 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
private lazy var authorLabelTopConstraint = authorLabel.pin(.top, to: .top, of: self)
private lazy var authorLabelHeightConstraint = authorLabel.set(.height, to: 0)
private lazy var profilePictureViewLeadingConstraint = profilePictureView.pin(.leading, to: .leading, of: self, withInset: VisibleMessageCell.groupThreadHSpacing)
private lazy var profilePictureViewWidthConstraint = profilePictureView.set(.width, to: Values.verySmallProfilePictureSize)
private lazy var contentViewLeadingConstraint1 = snContentView.pin(.leading, to: .trailing, of: profilePictureView, withInset: VisibleMessageCell.groupThreadHSpacing)
private lazy var contentViewLeadingConstraint2 = snContentView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: VisibleMessageCell.gutterSize)
private lazy var contentViewTopConstraint = snContentView.pin(.top, to: .bottom, of: authorLabel, withInset: VisibleMessageCell.authorLabelBottomSpacing)
@ -51,22 +50,13 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
private lazy var viewsToMoveForReply: [UIView] = [
snContentView,
profilePictureView,
moderatorIconImageView,
replyButton,
timerView,
messageStatusImageView,
reactionContainerView
]
private lazy var profilePictureView: ProfilePictureView = {
let result: ProfilePictureView = ProfilePictureView()
result.set(.height, to: Values.verySmallProfilePictureSize)
result.size = Values.verySmallProfilePictureSize
return result
}()
private lazy var moderatorIconImageView = UIImageView(image: #imageLiteral(resourceName: "Crown"))
private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .message)
lazy var bubbleBackgroundView: UIView = {
let result = UIView()
@ -176,7 +166,6 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
private static let messageStatusImageViewSize: CGFloat = 12
private static let authorLabelBottomSpacing: CGFloat = 4
private static let groupThreadHSpacing: CGFloat = 12
private static let profilePictureSize = Values.verySmallProfilePictureSize
private static let authorLabelInset: CGFloat = 12
private static let replyButtonSize: CGFloat = 24
private static let maxBubbleTranslationX: CGFloat = 40
@ -186,7 +175,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
static let contactThreadHSpacing = Values.mediumSpacing
static var gutterSize: CGFloat = {
var result = groupThreadHSpacing + profilePictureSize + groupThreadHSpacing
var result = groupThreadHSpacing + ProfilePictureView.Size.message.viewSize + groupThreadHSpacing
if UIDevice.current.isIPad {
result += 168
@ -195,7 +184,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
return result
}()
static var leftGutterSize: CGFloat { groupThreadHSpacing + profilePictureSize + groupThreadHSpacing }
static var leftGutterSize: CGFloat { groupThreadHSpacing + ProfilePictureView.Size.message.viewSize + groupThreadHSpacing }
// MARK: Direction & Position
@ -214,21 +203,13 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
// Profile picture view
addSubview(profilePictureView)
profilePictureViewLeadingConstraint.isActive = true
profilePictureViewWidthConstraint.isActive = true
// Moderator icon image view
moderatorIconImageView.set(.width, to: 20)
moderatorIconImageView.set(.height, to: 20)
addSubview(moderatorIconImageView)
moderatorIconImageView.pin(.trailing, to: .trailing, of: profilePictureView, withInset: 1)
moderatorIconImageView.pin(.bottom, to: .bottom, of: profilePictureView, withInset: 4.5)
// Content view
addSubview(snContentView)
contentViewLeadingConstraint1.isActive = true
contentViewTopConstraint.isActive = true
contentViewTrailingConstraint1.isActive = true
snContentView.pin(.bottom, to: .bottom, of: profilePictureView, withInset: -1)
snContentView.pin(.bottom, to: .bottom, of: profilePictureView)
// Bubble background view
bubbleBackgroundView.addSubview(bubbleView)
@ -317,14 +298,13 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
// Profile picture view
profilePictureViewLeadingConstraint.constant = (isGroupThread ? VisibleMessageCell.groupThreadHSpacing : 0)
profilePictureViewWidthConstraint.constant = (isGroupThread ? VisibleMessageCell.profilePictureSize : 0)
profilePictureView.isHidden = (!cellViewModel.shouldShowProfile || cellViewModel.profile == nil)
profilePictureView.update(
publicKey: cellViewModel.authorId,
profile: cellViewModel.profile,
icon: (cellViewModel.isSenderOpenGroupModerator ? .crown : .none),
threadVariant: cellViewModel.threadVariant
)
moderatorIconImageView.isHidden = (!cellViewModel.isSenderOpenGroupModerator || !cellViewModel.shouldShowProfile)
// Bubble view
contentViewLeadingConstraint1.isActive = (

View File

@ -477,19 +477,15 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
private func updateNavBarButtons() {
// Profile picture view
let profilePictureSize = Values.verySmallProfilePictureSize
let profilePictureView = ProfilePictureView()
let profilePictureView = ProfilePictureView(size: .navigation)
profilePictureView.accessibilityIdentifier = "User settings"
profilePictureView.accessibilityLabel = "User settings"
profilePictureView.isAccessibilityElement = true
profilePictureView.size = profilePictureSize
profilePictureView.update(
publicKey: getUserHexEncodedPublicKey(),
profile: Profile.fetchOrCreateCurrentUser(),
threadVariant: .contact
)
profilePictureView.set(.width, to: profilePictureSize)
profilePictureView.set(.height, to: profilePictureSize)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
profilePictureView.addGestureRecognizer(tapGestureRecognizer)

View File

@ -2,6 +2,7 @@
import UIKit
import SessionUIKit
import SignalUtilitiesKit
class MessageRequestsCell: UITableViewCell {
static let reuseIdentifier = "MessageRequestsCell"
@ -29,7 +30,7 @@ class MessageRequestsCell: UITableViewCell {
result.translatesAutoresizingMaskIntoConstraints = false
result.clipsToBounds = true
result.themeBackgroundColor = .conversationButton_unreadBubbleBackground
result.layer.cornerRadius = (Values.mediumProfilePictureSize / 2)
result.layer.cornerRadius = (ProfilePictureView.Size.list.viewSize / 2)
return result
}()
@ -100,8 +101,8 @@ class MessageRequestsCell: UITableViewCell {
constant: (Values.accentLineThickness + Values.mediumSpacing)
),
iconContainerView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
iconContainerView.widthAnchor.constraint(equalToConstant: Values.mediumProfilePictureSize),
iconContainerView.heightAnchor.constraint(equalToConstant: Values.mediumProfilePictureSize),
iconContainerView.widthAnchor.constraint(equalToConstant: ProfilePictureView.Size.list.viewSize),
iconContainerView.heightAnchor.constraint(equalToConstant: ProfilePictureView.Size.list.viewSize),
iconImageView.centerXAnchor.constraint(equalTo: iconContainerView.centerXAnchor),
iconImageView.centerYAnchor.constraint(equalTo: iconContainerView.centerYAnchor),

View File

@ -8,7 +8,7 @@ import SignalUtilitiesKit
class BlockedContactCell: UITableViewCell {
// MARK: - Components
private lazy var profilePictureView: ProfilePictureView = ProfilePictureView()
private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .list)
private let selectionView: RadioButton = {
let result: RadioButton = RadioButton(size: .medium)
@ -61,9 +61,6 @@ class BlockedContactCell: UITableViewCell {
.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -Values.mediumSpacing)
.isActive = true
profilePictureView.pin(.left, to: .left, of: contentView, withInset: Values.veryLargeSpacing)
profilePictureView.set(.width, to: Values.mediumProfilePictureSize)
profilePictureView.set(.height, to: Values.mediumProfilePictureSize)
profilePictureView.size = Values.mediumProfilePictureSize
selectionView.center(.vertical, in: contentView)
selectionView.topAnchor

View File

@ -18,7 +18,7 @@ public final class FullConversationCell: UITableViewCell {
private let accentLineView: UIView = UIView()
private lazy var profilePictureView: ProfilePictureView = ProfilePictureView()
private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .list)
private lazy var displayNameLabel: UILabel = {
let result: UILabel = UILabel()
@ -159,12 +159,6 @@ public final class FullConversationCell: UITableViewCell {
accentLineView.set(.width, to: Values.accentLineThickness)
accentLineView.set(.height, to: cellHeight)
// Profile picture view
let profilePictureViewSize = Values.mediumProfilePictureSize
profilePictureView.set(.width, to: profilePictureViewSize)
profilePictureView.set(.height, to: profilePictureViewSize)
profilePictureView.size = profilePictureViewSize
// Unread count view
unreadCountView.addSubview(unreadCountLabel)
unreadCountLabel.setCompressionResistanceHigh()
@ -640,7 +634,7 @@ public final class FullConversationCell: UITableViewCell {
// This method determines if the content is probably too long and returns the truncated or untruncated
// content accordingly
func truncatingIfNeeded(approxWidth: CGFloat, content: NSAttributedString) -> NSAttributedString {
let approxFullWidth: CGFloat = (approxWidth + profilePictureView.size + (Values.mediumSpacing * 3))
let approxFullWidth: CGFloat = (approxWidth + profilePictureView.size.viewSize + (Values.mediumSpacing * 3))
guard ((bounds.width - approxFullWidth) < 0) else { return content }

View File

@ -51,11 +51,10 @@ class SessionAvatarCell: UITableViewCell {
}()
fileprivate let profilePictureView: ProfilePictureView = {
let view: ProfilePictureView = ProfilePictureView()
let view: ProfilePictureView = ProfilePictureView(size: .hero)
view.accessibilityLabel = "Profile picture"
view.isAccessibilityElement = true
view.translatesAutoresizingMaskIntoConstraints = false
view.size = Values.largeProfilePictureSize
return view
}()
@ -148,9 +147,6 @@ class SessionAvatarCell: UITableViewCell {
private func setupLayout() {
stackView.pin(to: contentView)
profilePictureView.set(.width, to: profilePictureView.size)
profilePictureView.set(.height, to: profilePictureView.size)
displayNameLabel.pin(to: displayNameContainer)
displayNameTextField.center(in: displayNameContainer)
displayNameTextField.widthAnchor

View File

@ -141,12 +141,8 @@ extension SessionCell {
}()
private lazy var profilePictureView: ProfilePictureView = {
let result: ProfilePictureView = ProfilePictureView()
result.translatesAutoresizingMaskIntoConstraints = false
result.size = Values.smallProfilePictureSize
let result: ProfilePictureView = ProfilePictureView(size: .list)
result.isHidden = true
result.set(.width, to: Values.smallProfilePictureSize)
result.set(.height, to: Values.smallProfilePictureSize)
return result
}()

View File

@ -39,7 +39,7 @@ final class SimplifiedConversationCell: UITableViewCell {
}()
private lazy var profilePictureView: ProfilePictureView = {
let view: ProfilePictureView = ProfilePictureView()
let view: ProfilePictureView = ProfilePictureView(size: .list)
view.translatesAutoresizingMaskIntoConstraints = false
return view
@ -79,10 +79,6 @@ final class SimplifiedConversationCell: UITableViewCell {
accentLineView.set(.width, to: Values.accentLineThickness)
accentLineView.set(.height, to: 68)
profilePictureView.set(.width, to: Values.mediumProfilePictureSize)
profilePictureView.set(.height, to: Values.mediumProfilePictureSize)
profilePictureView.size = Values.mediumProfilePictureSize
stackView.pin(to: self)
}

View File

@ -108,6 +108,11 @@ internal enum Theme_ClassicDark: ThemeColors {
.reactions_contextMoreBackground: .classicDark1,
// NewConversation
.newConversation_background: .classicDark1
.newConversation_background: .classicDark1,
// Profile
.profileIcon: .primary,
.profileIcon_greenPrimaryColor: .black,
.profileIcon_background: .white
]
}

View File

@ -108,6 +108,11 @@ internal enum Theme_ClassicLight: ThemeColors {
.reactions_contextMoreBackground: .classicLight6,
// NewConversation
.newConversation_background: .classicLight6
.newConversation_background: .classicLight6,
// Profile
.profileIcon: .primary,
.profileIcon_greenPrimaryColor: .primary,
.profileIcon_background: .black
]
}

View File

@ -108,6 +108,11 @@ internal enum Theme_OceanDark: ThemeColors {
.reactions_contextMoreBackground: .oceanDark2,
// NewConversation
.newConversation_background: .oceanDark3
.newConversation_background: .oceanDark3,
// Profile
.profileIcon: .primary,
.profileIcon_greenPrimaryColor: .black,
.profileIcon_background: .white
]
}

View File

@ -108,6 +108,11 @@ internal enum Theme_OceanLight: ThemeColors {
.reactions_contextMoreBackground: .oceanLight6,
// NewConversation
.newConversation_background: .oceanLight7
.newConversation_background: .oceanLight7,
// Profile
.profileIcon: .primary,
.profileIcon_greenPrimaryColor: .primary,
.profileIcon_background: .oceanLight1
]
}

View File

@ -197,6 +197,11 @@ public indirect enum ThemeValue: Hashable {
// NewConversation
case newConversation_background
// Profile
case profileIcon
case profileIcon_greenPrimaryColor
case profileIcon_background
}
// MARK: - ForcedThemeValue

View File

@ -25,11 +25,6 @@ public final class Values : NSObject {
@objc public static let accentLineThickness = CGFloat(4)
@objc public static let verySmallProfilePictureSize = CGFloat(26)
@objc public static let smallProfilePictureSize = CGFloat(33)
@objc public static let mediumProfilePictureSize = CGFloat(45)
@objc public static let largeProfilePictureSize = CGFloat(75)
@objc public static let searchBarHeight = CGFloat(36)
@objc public static var separatorThickness: CGFloat { return 1 / UIScreen.main.scale }

View File

@ -7,14 +7,107 @@ import SessionUIKit
import SessionMessagingKit
public final class ProfilePictureView: UIView {
private var hasTappableProfilePicture: Bool = false
public var size: CGFloat = 0
public enum Size {
case navigation
case message
case list
case hero
public var viewSize: CGFloat {
switch self {
case .navigation, .message: return 26
case .list: return 46
case .hero: return 110
}
}
var imageSize: CGFloat {
switch self {
case .navigation, .message: return 26
case .list: return 46
case .hero: return 80
}
}
var multiImageSize: CGFloat {
switch self {
case .navigation, .message: return 18 // Shouldn't be used
case .list: return 32
case .hero: return 80
}
}
var iconSize: CGFloat {
switch self {
case .navigation, .message: return 8
case .list: return 16
case .hero: return 24
}
}
var iconVerticalInset: CGFloat {
switch self {
case .navigation, .message: return 1
case .list: return 3
case .hero: return 5
}
}
}
// Constraints
public enum ProfileIcon {
case none
case crown
case rightPlus
}
public var size: Size {
didSet {
widthConstraint.constant = (customWidth ?? size.viewSize)
heightConstraint.constant = size.viewSize
profileIconTopConstraint.constant = size.iconVerticalInset
profileIconBottomConstraint.constant = -size.iconVerticalInset
profileIconBackgroundWidthConstraint.constant = size.iconSize
profileIconBackgroundHeightConstraint.constant = size.iconSize
additionalProfileIconTopConstraint.constant = size.iconVerticalInset
additionalProfileIconBottomConstraint.constant = -size.iconVerticalInset
additionalProfileIconBackgroundWidthConstraint.constant = size.iconSize
additionalProfileIconBackgroundHeightConstraint.constant = size.iconSize
profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2)
additionalProfileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2)
}
}
public var customWidth: CGFloat? {
didSet {
self.widthConstraint.constant = (customWidth ?? self.size.viewSize)
}
}
private var hasTappableProfilePicture: Bool = false
// MARK: - Constraints
private var widthConstraint: NSLayoutConstraint!
private var heightConstraint: NSLayoutConstraint!
private var imageViewTopConstraint: NSLayoutConstraint!
private var imageViewLeadingConstraint: NSLayoutConstraint!
private var imageViewCenterXConstraint: NSLayoutConstraint!
private var imageViewCenterYConstraint: NSLayoutConstraint!
private var imageViewWidthConstraint: NSLayoutConstraint!
private var imageViewHeightConstraint: NSLayoutConstraint!
private var additionalImageViewWidthConstraint: NSLayoutConstraint!
private var additionalImageViewHeightConstraint: NSLayoutConstraint!
private var profileIconTopConstraint: NSLayoutConstraint!
private var profileIconBottomConstraint: NSLayoutConstraint!
private var profileIconBackgroundLeftAlignConstraint: NSLayoutConstraint!
private var profileIconBackgroundRightAlignConstraint: NSLayoutConstraint!
private var profileIconBackgroundWidthConstraint: NSLayoutConstraint!
private var profileIconBackgroundHeightConstraint: NSLayoutConstraint!
private var additionalProfileIconTopConstraint: NSLayoutConstraint!
private var additionalProfileIconBottomConstraint: NSLayoutConstraint!
private var additionalProfileIconBackgroundLeftAlignConstraint: NSLayoutConstraint!
private var additionalProfileIconBackgroundRightAlignConstraint: NSLayoutConstraint!
private var additionalProfileIconBackgroundWidthConstraint: NSLayoutConstraint!
private var additionalProfileIconBackgroundHeightConstraint: NSLayoutConstraint!
// MARK: - Components
@ -51,7 +144,6 @@ public final class ProfilePictureView: UIView {
result.clipsToBounds = true
result.themeBackgroundColor = .primary
result.themeBorderColor = .backgroundPrimary
result.layer.cornerRadius = (Values.smallProfilePictureSize / 2)
result.isHidden = true
return result
@ -88,33 +180,72 @@ public final class ProfilePictureView: UIView {
return result
}()
private lazy var profileIconBackgroundView: UIView = {
let result: UIView = UIView()
result.isHidden = true
return result
}()
private lazy var profileIconImageView: UIImageView = {
let result: UIImageView = UIImageView()
result.contentMode = .scaleAspectFit
return result
}()
private lazy var additionalProfileIconBackgroundView: UIView = {
let result: UIView = UIView()
result.isHidden = true
return result
}()
private lazy var additionalProfileIconImageView: UIImageView = {
let result: UIImageView = UIImageView()
result.contentMode = .scaleAspectFit
return result
}()
// MARK: - Lifecycle
public override init(frame: CGRect) {
super.init(frame: frame)
public init(size: Size) {
self.size = size
super.init(frame: CGRect(x: 0, y: 0, width: size.viewSize, height: size.viewSize))
setUpViewHierarchy()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
preconditionFailure("Use init(size:) instead.")
}
private func setUpViewHierarchy() {
let imageViewSize = CGFloat(Values.mediumProfilePictureSize)
let additionalImageViewSize = CGFloat(Values.smallProfilePictureSize)
addSubview(imageContainerView)
addSubview(profileIconBackgroundView)
addSubview(additionalImageContainerView)
addSubview(additionalProfileIconBackgroundView)
imageContainerView.pin(.leading, to: .leading, of: self)
imageContainerView.pin(.top, to: .top, of: self)
imageViewWidthConstraint = imageContainerView.set(.width, to: imageViewSize)
imageViewHeightConstraint = imageContainerView.set(.height, to: imageViewSize)
profileIconBackgroundView.addSubview(profileIconImageView)
additionalProfileIconBackgroundView.addSubview(additionalProfileIconImageView)
widthConstraint = self.set(.width, to: self.size.viewSize)
heightConstraint = self.set(.height, to: self.size.viewSize)
imageViewTopConstraint = imageContainerView.pin(.top, to: .top, of: self)
imageViewLeadingConstraint = imageContainerView.pin(.leading, to: .leading, of: self)
imageViewCenterXConstraint = imageContainerView.center(.horizontal, in: self)
imageViewCenterXConstraint.isActive = false
imageViewCenterYConstraint = imageContainerView.center(.vertical, in: self)
imageViewCenterYConstraint.isActive = false
imageViewWidthConstraint = imageContainerView.set(.width, to: size.imageSize)
imageViewHeightConstraint = imageContainerView.set(.height, to: size.imageSize)
additionalImageContainerView.pin(.trailing, to: .trailing, of: self)
additionalImageContainerView.pin(.bottom, to: .bottom, of: self)
additionalImageViewWidthConstraint = additionalImageContainerView.set(.width, to: additionalImageViewSize)
additionalImageViewHeightConstraint = additionalImageContainerView.set(.height, to: additionalImageViewSize)
additionalImageViewWidthConstraint = additionalImageContainerView.set(.width, to: size.multiImageSize)
additionalImageViewHeightConstraint = additionalImageContainerView.set(.height, to: size.multiImageSize)
imageContainerView.addSubview(imageView)
imageContainerView.addSubview(animatedImageView)
@ -131,32 +262,137 @@ public final class ProfilePictureView: UIView {
additionalProfilePlaceholderImageView.pin(.left, to: .left, of: additionalImageContainerView)
additionalProfilePlaceholderImageView.pin(.right, to: .right, of: additionalImageContainerView)
additionalProfilePlaceholderImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 5)
profileIconTopConstraint = profileIconImageView.pin(
.top,
to: .top,
of: profileIconBackgroundView,
withInset: size.iconVerticalInset
)
profileIconImageView.pin(.left, to: .left, of: profileIconBackgroundView)
profileIconImageView.pin(.right, to: .right, of: profileIconBackgroundView)
profileIconBottomConstraint = profileIconImageView.pin(
.bottom,
to: .bottom,
of: profileIconBackgroundView,
withInset: -size.iconVerticalInset
)
profileIconBackgroundLeftAlignConstraint = profileIconBackgroundView.pin(.leading, to: .leading, of: imageContainerView)
profileIconBackgroundRightAlignConstraint = profileIconBackgroundView.pin(.trailing, to: .trailing, of: imageContainerView)
profileIconBackgroundView.pin(.bottom, to: .bottom, of: imageContainerView)
profileIconBackgroundWidthConstraint = profileIconBackgroundView.set(.width, to: size.iconSize)
profileIconBackgroundHeightConstraint = profileIconBackgroundView.set(.height, to: size.iconSize)
profileIconBackgroundLeftAlignConstraint.isActive = false
profileIconBackgroundRightAlignConstraint.isActive = false
additionalProfileIconTopConstraint = additionalProfileIconImageView.pin(
.top,
to: .top,
of: additionalProfileIconBackgroundView,
withInset: size.iconVerticalInset
)
additionalProfileIconImageView.pin(.left, to: .left, of: additionalProfileIconBackgroundView)
additionalProfileIconImageView.pin(.right, to: .right, of: additionalProfileIconBackgroundView)
additionalProfileIconBottomConstraint = additionalProfileIconImageView.pin(
.bottom,
to: .bottom,
of: additionalProfileIconBackgroundView,
withInset: -size.iconVerticalInset
)
additionalProfileIconBackgroundLeftAlignConstraint = additionalProfileIconBackgroundView.pin(.leading, to: .leading, of: additionalImageContainerView)
additionalProfileIconBackgroundRightAlignConstraint = additionalProfileIconBackgroundView.pin(.trailing, to: .trailing, of: additionalImageContainerView)
additionalProfileIconBackgroundView.pin(.bottom, to: .bottom, of: additionalImageContainerView)
additionalProfileIconBackgroundWidthConstraint = additionalProfileIconBackgroundView.set(.width, to: size.iconSize)
additionalProfileIconBackgroundHeightConstraint = additionalProfileIconBackgroundView.set(.height, to: size.iconSize)
additionalProfileIconBackgroundLeftAlignConstraint.isActive = false
additionalProfileIconBackgroundRightAlignConstraint.isActive = false
}
// MARK: - Content
private func updateIconView(
icon: ProfileIcon,
imageView: UIImageView,
backgroundView: UIView,
leftAlignConstraint: NSLayoutConstraint,
rightAlignConstraint: NSLayoutConstraint
) {
backgroundView.isHidden = (icon == .none)
leftAlignConstraint.isActive = (
icon == .none ||
icon == .crown
)
rightAlignConstraint.isActive = (
icon == .rightPlus
)
switch icon {
case .none: imageView.image = nil
case .crown:
imageView.image = UIImage(systemName: "crown.fill")
backgroundView.themeBackgroundColor = .profileIcon_background
ThemeManager.onThemeChange(observer: imageView) { [weak imageView] _, primaryColor in
let targetColor: ThemeValue = (primaryColor == .green ?
.profileIcon_greenPrimaryColor :
.profileIcon
)
guard imageView?.themeTintColor != targetColor else { return }
imageView?.themeTintColor = targetColor
}
case .rightPlus:
imageView.image = UIImage(systemName: "plus")
imageView.themeTintColor = .black
backgroundView.themeBackgroundColor = .primary
}
}
public func update(
publicKey: String = "",
profile: Profile? = nil,
icon: ProfileIcon = .none,
additionalProfile: Profile? = nil,
additionalIcon: ProfileIcon = .none,
threadVariant: SessionThread.Variant,
openGroupProfilePictureData: Data? = nil,
useFallbackPicture: Bool = false,
showMultiAvatarForClosedGroup: Bool = false
) {
AssertIsOnMainThread()
// Sort out the profile icon first
updateIconView(
icon: icon,
imageView: profileIconImageView,
backgroundView: profileIconBackgroundView,
leftAlignConstraint: profileIconBackgroundLeftAlignConstraint,
rightAlignConstraint: profileIconBackgroundRightAlignConstraint
)
guard !useFallbackPicture else {
switch self.size {
case Values.smallProfilePictureSize..<Values.mediumProfilePictureSize: imageView.image = #imageLiteral(resourceName: "SessionWhite16")
case Values.mediumProfilePictureSize..<Values.largeProfilePictureSize: imageView.image = #imageLiteral(resourceName: "SessionWhite24")
default: imageView.image = #imageLiteral(resourceName: "SessionWhite40")
case .navigation, .message: imageView.image = #imageLiteral(resourceName: "SessionWhite16")
case .list: imageView.image = #imageLiteral(resourceName: "SessionWhite24")
case .hero: imageView.image = #imageLiteral(resourceName: "SessionWhite40")
}
imageView.contentMode = .center
imageView.isHidden = false
animatedImageView.isHidden = true
imageContainerView.themeBackgroundColorForced = .theme(.classicDark, color: .borderSeparator)
imageContainerView.layer.cornerRadius = (self.size / 2)
imageViewWidthConstraint.constant = self.size
imageViewHeightConstraint.constant = self.size
imageContainerView.layer.cornerRadius = (self.size.imageSize / 2)
imageViewWidthConstraint.constant = self.size.imageSize
imageViewHeightConstraint.constant = self.size.imageSize
profileIconBackgroundWidthConstraint.constant = self.size.iconSize
profileIconBackgroundHeightConstraint.constant = self.size.iconSize
profileIconBackgroundView.layer.cornerRadius = (self.size.iconSize / 2)
additionalProfileIconBackgroundWidthConstraint.constant = self.size.iconSize
additionalProfileIconBackgroundHeightConstraint.constant = self.size.iconSize
additionalProfileIconBackgroundView.layer.cornerRadius = (self.size.iconSize / 2)
additionalImageContainerView.isHidden = true
animatedImageView.image = nil
additionalImageView.image = nil
@ -203,29 +439,29 @@ public final class ProfilePictureView: UIView {
switch (threadVariant, showMultiAvatarForClosedGroup) {
case (.closedGroup, true):
if self.size == 40 {
targetSize = 32
}
else if self.size == Values.largeProfilePictureSize {
targetSize = 56
}
else {
targetSize = Values.smallProfilePictureSize
}
imageViewWidthConstraint.constant = targetSize
imageViewHeightConstraint.constant = targetSize
additionalImageViewWidthConstraint.constant = targetSize
additionalImageViewHeightConstraint.constant = targetSize
targetSize = self.size.multiImageSize
additionalImageContainerView.isHidden = false
imageViewTopConstraint.isActive = true
imageViewLeadingConstraint.isActive = true
imageViewCenterXConstraint.isActive = false
imageViewCenterYConstraint.isActive = false
// Sort out the additinoal profile icon if needed
updateIconView(
icon: additionalIcon,
imageView: additionalProfileIconImageView,
backgroundView: additionalProfileIconBackgroundView,
leftAlignConstraint: additionalProfileIconBackgroundLeftAlignConstraint,
rightAlignConstraint: additionalProfileIconBackgroundRightAlignConstraint
)
if let additionalProfile: Profile = additionalProfile {
let (image, animatedImage, _): (UIImage?, YYImage?, Bool) = getProfilePicture(
of: targetSize,
of: self.size.multiImageSize,
for: additionalProfile.id,
profile: additionalProfile
)
// Set the images and show the appropriate imageView (non-animated should be
// visible if there is no image)
additionalImageView.image = image
@ -241,15 +477,19 @@ public final class ProfilePictureView: UIView {
}
default:
targetSize = self.size
imageViewWidthConstraint.constant = targetSize
imageViewHeightConstraint.constant = targetSize
targetSize = self.size.imageSize
additionalImageContainerView.isHidden = true
additionalProfileIconBackgroundView.isHidden = true
additionalImageView.image = nil
additionalImageView.isHidden = true
additionalAnimatedImageView.image = nil
additionalAnimatedImageView.isHidden = true
additionalProfilePlaceholderImageView.isHidden = true
imageViewTopConstraint.isActive = false
imageViewLeadingConstraint.isActive = false
imageViewCenterXConstraint.isActive = true
imageViewCenterYConstraint.isActive = true
}
// Set the image
@ -287,8 +527,14 @@ public final class ProfilePictureView: UIView {
imageView.contentMode = .scaleAspectFill
animatedImageView.contentMode = .scaleAspectFill
imageContainerView.themeBackgroundColor = .backgroundSecondary
imageViewWidthConstraint.constant = targetSize
imageViewHeightConstraint.constant = targetSize
imageContainerView.layer.cornerRadius = (targetSize / 2)
additionalImageViewWidthConstraint.constant = targetSize
additionalImageViewHeightConstraint.constant = targetSize
additionalImageContainerView.layer.cornerRadius = (targetSize / 2)
profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2)
additionalProfileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2)
}
// MARK: - Convenience