diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index b33177ff7..cf7ac6184 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -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 } diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index cecb6c01b..8ed425011 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -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() diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 17ab87f7d..f521f6f01 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -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) diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index 4727b53fa..a2020e8be 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -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() diff --git a/Session/Conversations/Input View/MentionSelectionView.swift b/Session/Conversations/Input View/MentionSelectionView.swift index 881059cc3..b499af0b9 100644 --- a/Session/Conversations/Input View/MentionSelectionView.swift +++ b/Session/Conversations/Input View/MentionSelectionView.swift @@ -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 } } diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index fc1c853a3..df081981c 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -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 = ( diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 3475539da..1abd0f503 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -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) diff --git a/Session/Home/Message Requests/Views/MessageRequestsCell.swift b/Session/Home/Message Requests/Views/MessageRequestsCell.swift index 5141500eb..61feef7b3 100644 --- a/Session/Home/Message Requests/Views/MessageRequestsCell.swift +++ b/Session/Home/Message Requests/Views/MessageRequestsCell.swift @@ -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), diff --git a/Session/Settings/Views/BlockedContactCell.swift b/Session/Settings/Views/BlockedContactCell.swift index 3cf838fb8..261de3719 100644 --- a/Session/Settings/Views/BlockedContactCell.swift +++ b/Session/Settings/Views/BlockedContactCell.swift @@ -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 diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index c2de9ffa3..611ac20af 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -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 } diff --git a/Session/Shared/Views/SessionAvatarCell.swift b/Session/Shared/Views/SessionAvatarCell.swift index b7f827946..9dee6402a 100644 --- a/Session/Shared/Views/SessionAvatarCell.swift +++ b/Session/Shared/Views/SessionAvatarCell.swift @@ -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 diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index 5b43c9cd0..7a625410a 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -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 }() diff --git a/SessionShareExtension/SimplifiedConversationCell.swift b/SessionShareExtension/SimplifiedConversationCell.swift index 3c2fa1e32..db7cf2c55 100644 --- a/SessionShareExtension/SimplifiedConversationCell.swift +++ b/SessionShareExtension/SimplifiedConversationCell.swift @@ -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) } diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift index 69242e54b..b23d07000 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift @@ -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 ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift index f1e1a9d80..67f9f9ce1 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift @@ -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 ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift index beb4cba54..78adeab56 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift @@ -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 ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift index c1e72799e..a71b96c11 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift @@ -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 ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme.swift b/SessionUIKit/Style Guide/Themes/Theme.swift index f2766cc6d..3e2f30e62 100644 --- a/SessionUIKit/Style Guide/Themes/Theme.swift +++ b/SessionUIKit/Style Guide/Themes/Theme.swift @@ -197,6 +197,11 @@ public indirect enum ThemeValue: Hashable { // NewConversation case newConversation_background + + // Profile + case profileIcon + case profileIcon_greenPrimaryColor + case profileIcon_background } // MARK: - ForcedThemeValue diff --git a/SessionUIKit/Style Guide/Values.swift b/SessionUIKit/Style Guide/Values.swift index a66d47801..fd7cc1012 100644 --- a/SessionUIKit/Style Guide/Values.swift +++ b/SessionUIKit/Style Guide/Values.swift @@ -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 } diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift index 1f87c047f..79f973472 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift @@ -7,14 +7,107 @@ import SessionUIKit import SessionMessagingKit public final class ProfilePictureView: UIView { + 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 + } + } + } + + 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 - public var size: CGFloat = 0 - // Constraints + // 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) + + profileIconBackgroundView.addSubview(profileIconImageView) + additionalProfileIconBackgroundView.addSubview(additionalProfileIconImageView) + + widthConstraint = self.set(.width, to: self.size.viewSize) + heightConstraint = self.set(.height, to: self.size.viewSize) - 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) + 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..