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 return
} }
DispatchQueue.main.async {
guard CurrentAppContext().isMainAppAndActive else { return } guard CurrentAppContext().isMainAppAndActive else { return }
DispatchQueue.main.async {
guard let presentingVC = CurrentAppContext().frontmostViewController() else { guard let presentingVC = CurrentAppContext().frontmostViewController() else {
preconditionFailure() // FIXME: Handle more gracefully preconditionFailure() // FIXME: Handle more gracefully
} }

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import UIKit
import SessionUIKit import SessionUIKit
import SessionMessagingKit import SessionMessagingKit
import SessionUtilitiesKit import SessionUtilitiesKit
import SignalUtilitiesKit
final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, MentionSelectionViewDelegate { final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, MentionSelectionViewDelegate {
// MARK: - Variables // MARK: - Variables
@ -491,7 +492,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M
func showMentionsUI(for candidates: [MentionInfo]) { func showMentionsUI(for candidates: [MentionInfo]) {
mentionsView.candidates = candidates 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 mentionsViewHeightConstraint.constant = CGFloat(min(3, candidates.count)) * mentionCellHeight
layoutIfNeeded() layoutIfNeeded()

View File

@ -111,9 +111,7 @@ private extension MentionSelectionView {
final class Cell: UITableViewCell { final class Cell: UITableViewCell {
// MARK: - UI // MARK: - UI
private lazy var profilePictureView: ProfilePictureView = ProfilePictureView() private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .message)
private lazy var moderatorIconImageView: UIImageView = UIImageView(image: #imageLiteral(resourceName: "Crown"))
private lazy var displayNameLabel: UILabel = { private lazy var displayNameLabel: UILabel = {
let result: UILabel = UILabel() let result: UILabel = UILabel()
@ -155,18 +153,12 @@ private extension MentionSelectionView {
selectedBackgroundView.themeBackgroundColor = .highlighted(.settings_tabBackground) selectedBackgroundView.themeBackgroundColor = .highlighted(.settings_tabBackground)
self.selectedBackgroundView = selectedBackgroundView 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 // Main stack view
let mainStackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameLabel ]) let mainStackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameLabel ])
mainStackView.axis = .horizontal mainStackView.axis = .horizontal
mainStackView.alignment = .center mainStackView.alignment = .center
mainStackView.spacing = Values.mediumSpacing mainStackView.spacing = Values.mediumSpacing
mainStackView.set(.height, to: profilePictureViewSize) mainStackView.set(.height, to: ProfilePictureView.Size.message.viewSize)
contentView.addSubview(mainStackView) contentView.addSubview(mainStackView)
mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.mediumSpacing) mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.mediumSpacing)
mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.smallSpacing) 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) contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.smallSpacing)
mainStackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing) 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 // Separator
addSubview(separator) addSubview(separator)
separator.pin(.leading, to: .leading, of: self) separator.pin(.leading, to: .leading, of: self)
@ -200,9 +185,9 @@ private extension MentionSelectionView {
profilePictureView.update( profilePictureView.update(
publicKey: profile.id, publicKey: profile.id,
profile: profile, profile: profile,
icon: (isUserModeratorOrAdmin ? .crown : .none),
threadVariant: threadVariant threadVariant: threadVariant
) )
moderatorIconImageView.isHidden = !isUserModeratorOrAdmin
separator.isHidden = isLast 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 authorLabelTopConstraint = authorLabel.pin(.top, to: .top, of: self)
private lazy var authorLabelHeightConstraint = authorLabel.set(.height, to: 0) 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 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 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 contentViewLeadingConstraint2 = snContentView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: VisibleMessageCell.gutterSize)
private lazy var contentViewTopConstraint = snContentView.pin(.top, to: .bottom, of: authorLabel, withInset: VisibleMessageCell.authorLabelBottomSpacing) 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] = [ private lazy var viewsToMoveForReply: [UIView] = [
snContentView, snContentView,
profilePictureView, profilePictureView,
moderatorIconImageView,
replyButton, replyButton,
timerView, timerView,
messageStatusImageView, messageStatusImageView,
reactionContainerView reactionContainerView
] ]
private lazy var profilePictureView: ProfilePictureView = { private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .message)
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"))
lazy var bubbleBackgroundView: UIView = { lazy var bubbleBackgroundView: UIView = {
let result = UIView() let result = UIView()
@ -176,7 +166,6 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
private static let messageStatusImageViewSize: CGFloat = 12 private static let messageStatusImageViewSize: CGFloat = 12
private static let authorLabelBottomSpacing: CGFloat = 4 private static let authorLabelBottomSpacing: CGFloat = 4
private static let groupThreadHSpacing: CGFloat = 12 private static let groupThreadHSpacing: CGFloat = 12
private static let profilePictureSize = Values.verySmallProfilePictureSize
private static let authorLabelInset: CGFloat = 12 private static let authorLabelInset: CGFloat = 12
private static let replyButtonSize: CGFloat = 24 private static let replyButtonSize: CGFloat = 24
private static let maxBubbleTranslationX: CGFloat = 40 private static let maxBubbleTranslationX: CGFloat = 40
@ -186,7 +175,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
static let contactThreadHSpacing = Values.mediumSpacing static let contactThreadHSpacing = Values.mediumSpacing
static var gutterSize: CGFloat = { static var gutterSize: CGFloat = {
var result = groupThreadHSpacing + profilePictureSize + groupThreadHSpacing var result = groupThreadHSpacing + ProfilePictureView.Size.message.viewSize + groupThreadHSpacing
if UIDevice.current.isIPad { if UIDevice.current.isIPad {
result += 168 result += 168
@ -195,7 +184,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
return result return result
}() }()
static var leftGutterSize: CGFloat { groupThreadHSpacing + profilePictureSize + groupThreadHSpacing } static var leftGutterSize: CGFloat { groupThreadHSpacing + ProfilePictureView.Size.message.viewSize + groupThreadHSpacing }
// MARK: Direction & Position // MARK: Direction & Position
@ -214,21 +203,13 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
// Profile picture view // Profile picture view
addSubview(profilePictureView) addSubview(profilePictureView)
profilePictureViewLeadingConstraint.isActive = true 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 // Content view
addSubview(snContentView) addSubview(snContentView)
contentViewLeadingConstraint1.isActive = true contentViewLeadingConstraint1.isActive = true
contentViewTopConstraint.isActive = true contentViewTopConstraint.isActive = true
contentViewTrailingConstraint1.isActive = true contentViewTrailingConstraint1.isActive = true
snContentView.pin(.bottom, to: .bottom, of: profilePictureView, withInset: -1) snContentView.pin(.bottom, to: .bottom, of: profilePictureView)
// Bubble background view // Bubble background view
bubbleBackgroundView.addSubview(bubbleView) bubbleBackgroundView.addSubview(bubbleView)
@ -317,14 +298,13 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
// Profile picture view // Profile picture view
profilePictureViewLeadingConstraint.constant = (isGroupThread ? VisibleMessageCell.groupThreadHSpacing : 0) profilePictureViewLeadingConstraint.constant = (isGroupThread ? VisibleMessageCell.groupThreadHSpacing : 0)
profilePictureViewWidthConstraint.constant = (isGroupThread ? VisibleMessageCell.profilePictureSize : 0)
profilePictureView.isHidden = (!cellViewModel.shouldShowProfile || cellViewModel.profile == nil) profilePictureView.isHidden = (!cellViewModel.shouldShowProfile || cellViewModel.profile == nil)
profilePictureView.update( profilePictureView.update(
publicKey: cellViewModel.authorId, publicKey: cellViewModel.authorId,
profile: cellViewModel.profile, profile: cellViewModel.profile,
icon: (cellViewModel.isSenderOpenGroupModerator ? .crown : .none),
threadVariant: cellViewModel.threadVariant threadVariant: cellViewModel.threadVariant
) )
moderatorIconImageView.isHidden = (!cellViewModel.isSenderOpenGroupModerator || !cellViewModel.shouldShowProfile)
// Bubble view // Bubble view
contentViewLeadingConstraint1.isActive = ( contentViewLeadingConstraint1.isActive = (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -108,6 +108,11 @@ internal enum Theme_ClassicDark: ThemeColors {
.reactions_contextMoreBackground: .classicDark1, .reactions_contextMoreBackground: .classicDark1,
// NewConversation // 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, .reactions_contextMoreBackground: .classicLight6,
// NewConversation // 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, .reactions_contextMoreBackground: .oceanDark2,
// NewConversation // 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, .reactions_contextMoreBackground: .oceanLight6,
// NewConversation // 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 // NewConversation
case newConversation_background case newConversation_background
// Profile
case profileIcon
case profileIcon_greenPrimaryColor
case profileIcon_background
} }
// MARK: - ForcedThemeValue // MARK: - ForcedThemeValue

View File

@ -25,11 +25,6 @@ public final class Values : NSObject {
@objc public static let accentLineThickness = CGFloat(4) @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 let searchBarHeight = CGFloat(36)
@objc public static var separatorThickness: CGFloat { return 1 / UIScreen.main.scale } @objc public static var separatorThickness: CGFloat { return 1 / UIScreen.main.scale }

View File

@ -7,14 +7,107 @@ import SessionUIKit
import SessionMessagingKit import SessionMessagingKit
public final class ProfilePictureView: UIView { public final class ProfilePictureView: UIView {
private var hasTappableProfilePicture: Bool = false public enum Size {
public var size: CGFloat = 0 case navigation
case message
case list
case hero
// Constraints 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
}
}
}
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 imageViewWidthConstraint: NSLayoutConstraint!
private var imageViewHeightConstraint: NSLayoutConstraint! private var imageViewHeightConstraint: NSLayoutConstraint!
private var additionalImageViewWidthConstraint: NSLayoutConstraint! private var additionalImageViewWidthConstraint: NSLayoutConstraint!
private var additionalImageViewHeightConstraint: 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 // MARK: - Components
@ -51,7 +144,6 @@ public final class ProfilePictureView: UIView {
result.clipsToBounds = true result.clipsToBounds = true
result.themeBackgroundColor = .primary result.themeBackgroundColor = .primary
result.themeBorderColor = .backgroundPrimary result.themeBorderColor = .backgroundPrimary
result.layer.cornerRadius = (Values.smallProfilePictureSize / 2)
result.isHidden = true result.isHidden = true
return result return result
@ -88,33 +180,72 @@ public final class ProfilePictureView: UIView {
return result 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 // MARK: - Lifecycle
public override init(frame: CGRect) { public init(size: Size) {
super.init(frame: frame) self.size = size
super.init(frame: CGRect(x: 0, y: 0, width: size.viewSize, height: size.viewSize))
setUpViewHierarchy() setUpViewHierarchy()
} }
public required init?(coder: NSCoder) { public required init?(coder: NSCoder) {
super.init(coder: coder) preconditionFailure("Use init(size:) instead.")
setUpViewHierarchy()
} }
private func setUpViewHierarchy() { private func setUpViewHierarchy() {
let imageViewSize = CGFloat(Values.mediumProfilePictureSize)
let additionalImageViewSize = CGFloat(Values.smallProfilePictureSize)
addSubview(imageContainerView) addSubview(imageContainerView)
addSubview(profileIconBackgroundView)
addSubview(additionalImageContainerView) addSubview(additionalImageContainerView)
addSubview(additionalProfileIconBackgroundView)
imageContainerView.pin(.leading, to: .leading, of: self) profileIconBackgroundView.addSubview(profileIconImageView)
imageContainerView.pin(.top, to: .top, of: self) additionalProfileIconBackgroundView.addSubview(additionalProfileIconImageView)
imageViewWidthConstraint = imageContainerView.set(.width, to: imageViewSize)
imageViewHeightConstraint = imageContainerView.set(.height, to: imageViewSize) 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(.trailing, to: .trailing, of: self)
additionalImageContainerView.pin(.bottom, to: .bottom, of: self) additionalImageContainerView.pin(.bottom, to: .bottom, of: self)
additionalImageViewWidthConstraint = additionalImageContainerView.set(.width, to: additionalImageViewSize) additionalImageViewWidthConstraint = additionalImageContainerView.set(.width, to: size.multiImageSize)
additionalImageViewHeightConstraint = additionalImageContainerView.set(.height, to: additionalImageViewSize) additionalImageViewHeightConstraint = additionalImageContainerView.set(.height, to: size.multiImageSize)
imageContainerView.addSubview(imageView) imageContainerView.addSubview(imageView)
imageContainerView.addSubview(animatedImageView) imageContainerView.addSubview(animatedImageView)
@ -131,32 +262,137 @@ public final class ProfilePictureView: UIView {
additionalProfilePlaceholderImageView.pin(.left, to: .left, of: additionalImageContainerView) additionalProfilePlaceholderImageView.pin(.left, to: .left, of: additionalImageContainerView)
additionalProfilePlaceholderImageView.pin(.right, to: .right, of: additionalImageContainerView) additionalProfilePlaceholderImageView.pin(.right, to: .right, of: additionalImageContainerView)
additionalProfilePlaceholderImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 5) 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( public func update(
publicKey: String = "", publicKey: String = "",
profile: Profile? = nil, profile: Profile? = nil,
icon: ProfileIcon = .none,
additionalProfile: Profile? = nil, additionalProfile: Profile? = nil,
additionalIcon: ProfileIcon = .none,
threadVariant: SessionThread.Variant, threadVariant: SessionThread.Variant,
openGroupProfilePictureData: Data? = nil, openGroupProfilePictureData: Data? = nil,
useFallbackPicture: Bool = false, useFallbackPicture: Bool = false,
showMultiAvatarForClosedGroup: Bool = false showMultiAvatarForClosedGroup: Bool = false
) { ) {
AssertIsOnMainThread() AssertIsOnMainThread()
// Sort out the profile icon first
updateIconView(
icon: icon,
imageView: profileIconImageView,
backgroundView: profileIconBackgroundView,
leftAlignConstraint: profileIconBackgroundLeftAlignConstraint,
rightAlignConstraint: profileIconBackgroundRightAlignConstraint
)
guard !useFallbackPicture else { guard !useFallbackPicture else {
switch self.size { switch self.size {
case Values.smallProfilePictureSize..<Values.mediumProfilePictureSize: imageView.image = #imageLiteral(resourceName: "SessionWhite16") case .navigation, .message: imageView.image = #imageLiteral(resourceName: "SessionWhite16")
case Values.mediumProfilePictureSize..<Values.largeProfilePictureSize: imageView.image = #imageLiteral(resourceName: "SessionWhite24") case .list: imageView.image = #imageLiteral(resourceName: "SessionWhite24")
default: imageView.image = #imageLiteral(resourceName: "SessionWhite40") case .hero: imageView.image = #imageLiteral(resourceName: "SessionWhite40")
} }
imageView.contentMode = .center imageView.contentMode = .center
imageView.isHidden = false imageView.isHidden = false
animatedImageView.isHidden = true animatedImageView.isHidden = true
imageContainerView.themeBackgroundColorForced = .theme(.classicDark, color: .borderSeparator) imageContainerView.themeBackgroundColorForced = .theme(.classicDark, color: .borderSeparator)
imageContainerView.layer.cornerRadius = (self.size / 2) imageContainerView.layer.cornerRadius = (self.size.imageSize / 2)
imageViewWidthConstraint.constant = self.size imageViewWidthConstraint.constant = self.size.imageSize
imageViewHeightConstraint.constant = self.size 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 additionalImageContainerView.isHidden = true
animatedImageView.image = nil animatedImageView.image = nil
additionalImageView.image = nil additionalImageView.image = nil
@ -203,25 +439,25 @@ public final class ProfilePictureView: UIView {
switch (threadVariant, showMultiAvatarForClosedGroup) { switch (threadVariant, showMultiAvatarForClosedGroup) {
case (.closedGroup, true): case (.closedGroup, true):
if self.size == 40 { targetSize = self.size.multiImageSize
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
additionalImageContainerView.isHidden = false 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 { if let additionalProfile: Profile = additionalProfile {
let (image, animatedImage, _): (UIImage?, YYImage?, Bool) = getProfilePicture( let (image, animatedImage, _): (UIImage?, YYImage?, Bool) = getProfilePicture(
of: targetSize, of: self.size.multiImageSize,
for: additionalProfile.id, for: additionalProfile.id,
profile: additionalProfile profile: additionalProfile
) )
@ -241,15 +477,19 @@ public final class ProfilePictureView: UIView {
} }
default: default:
targetSize = self.size targetSize = self.size.imageSize
imageViewWidthConstraint.constant = targetSize
imageViewHeightConstraint.constant = targetSize
additionalImageContainerView.isHidden = true additionalImageContainerView.isHidden = true
additionalProfileIconBackgroundView.isHidden = true
additionalImageView.image = nil additionalImageView.image = nil
additionalImageView.isHidden = true additionalImageView.isHidden = true
additionalAnimatedImageView.image = nil additionalAnimatedImageView.image = nil
additionalAnimatedImageView.isHidden = true additionalAnimatedImageView.isHidden = true
additionalProfilePlaceholderImageView.isHidden = true additionalProfilePlaceholderImageView.isHidden = true
imageViewTopConstraint.isActive = false
imageViewLeadingConstraint.isActive = false
imageViewCenterXConstraint.isActive = true
imageViewCenterYConstraint.isActive = true
} }
// Set the image // Set the image
@ -287,8 +527,14 @@ public final class ProfilePictureView: UIView {
imageView.contentMode = .scaleAspectFill imageView.contentMode = .scaleAspectFill
animatedImageView.contentMode = .scaleAspectFill animatedImageView.contentMode = .scaleAspectFill
imageContainerView.themeBackgroundColor = .backgroundSecondary imageContainerView.themeBackgroundColor = .backgroundSecondary
imageViewWidthConstraint.constant = targetSize
imageViewHeightConstraint.constant = targetSize
imageContainerView.layer.cornerRadius = (targetSize / 2) imageContainerView.layer.cornerRadius = (targetSize / 2)
additionalImageViewWidthConstraint.constant = targetSize
additionalImageViewHeightConstraint.constant = targetSize
additionalImageContainerView.layer.cornerRadius = (targetSize / 2) additionalImageContainerView.layer.cornerRadius = (targetSize / 2)
profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2)
additionalProfileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2)
} }
// MARK: - Convenience // MARK: - Convenience