From 2d792e4e3e9858e8a30373d7fc024af16cede380 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 24 May 2023 13:33:53 +1000 Subject: [PATCH 1/3] 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 --- .../Call Management/SessionCallManager.swift | 4 +- .../Views & Modals/IncomingCallBanner.swift | 10 +- Session/Conversations/ConversationVC.swift | 6 +- .../Conversations/Input View/InputView.swift | 3 +- .../Input View/MentionSelectionView.swift | 21 +- .../Message Cells/VisibleMessageCell.swift | 30 +- Session/Home/HomeVC.swift | 6 +- .../Views/MessageRequestsCell.swift | 7 +- .../Settings/Views/BlockedContactCell.swift | 5 +- Session/Shared/FullConversationCell.swift | 10 +- Session/Shared/Views/SessionAvatarCell.swift | 6 +- .../Views/SessionCell+AccessoryView.swift | 6 +- .../SimplifiedConversationCell.swift | 6 +- .../Themes/Theme+ClassicDark.swift | 7 +- .../Themes/Theme+ClassicLight.swift | 7 +- .../Style Guide/Themes/Theme+OceanDark.swift | 7 +- .../Style Guide/Themes/Theme+OceanLight.swift | 7 +- SessionUIKit/Style Guide/Themes/Theme.swift | 5 + SessionUIKit/Style Guide/Values.swift | 5 - .../Profile Pictures/ProfilePictureView.swift | 330 +++++++++++++++--- 20 files changed, 344 insertions(+), 144 deletions(-) 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 { - 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.. Date: Wed, 24 May 2023 17:42:43 +1000 Subject: [PATCH 2/3] Updated the profile picture modal Moved the ProfilePictureView into SessionUIKit Fixed a couple of minor ProfilePictureView bugs --- Session.xcodeproj/project.pbxproj | 40 +- .../Calls/Call Management/SessionCall.swift | 3 +- .../CropScaleImageViewController.swift | 15 +- .../Translations/de.lproj/Localizable.strings | 2 +- .../Translations/en.lproj/Localizable.strings | 2 +- .../Translations/es.lproj/Localizable.strings | 2 +- .../Translations/fa.lproj/Localizable.strings | 2 +- .../Translations/fi.lproj/Localizable.strings | 2 +- .../Translations/fr.lproj/Localizable.strings | 2 +- .../Translations/hi.lproj/Localizable.strings | 2 +- .../Translations/hr.lproj/Localizable.strings | 2 +- .../id-ID.lproj/Localizable.strings | 2 +- .../Translations/it.lproj/Localizable.strings | 2 +- .../Translations/ja.lproj/Localizable.strings | 2 +- .../Translations/nl.lproj/Localizable.strings | 2 +- .../Translations/pl.lproj/Localizable.strings | 2 +- .../pt_BR.lproj/Localizable.strings | 2 +- .../Translations/ru.lproj/Localizable.strings | 2 +- .../Translations/si.lproj/Localizable.strings | 2 +- .../Translations/sk.lproj/Localizable.strings | 2 +- .../Translations/sv.lproj/Localizable.strings | 2 +- .../Translations/th.lproj/Localizable.strings | 2 +- .../vi-VN.lproj/Localizable.strings | 2 +- .../zh-Hant.lproj/Localizable.strings | 2 +- .../zh_CN.lproj/Localizable.strings | 2 +- Session/Settings/SettingsViewModel.swift | 48 +- .../ProfilePictureView+Convenience.swift | 211 +++++++++ .../Components/ConfirmationModal.swift | 50 +- .../Components}/PlaceholderIcon.swift | 61 ++- .../Components}/ProfilePictureView.swift | 441 +++++++++--------- .../Profile Pictures/Identicon+ObjC.swift | 57 --- 31 files changed, 585 insertions(+), 385 deletions(-) create mode 100644 SessionMessagingKit/Utilities/ProfilePictureView+Convenience.swift rename {SignalUtilitiesKit/Profile Pictures => SessionUIKit/Components}/PlaceholderIcon.swift (54%) rename {SignalUtilitiesKit/Profile Pictures => SessionUIKit/Components}/ProfilePictureView.swift (55%) delete mode 100644 SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 1f46f41af..50b28fed6 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -381,9 +381,6 @@ C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF23F255B6D67007E1867 /* NSAttributedString+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF24D255B6D67007E1867 /* UIView+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF240255B6D67007E1867 /* UIView+OWS.swift */; }; C38EF24E255B6D67007E1867 /* Collection+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF241255B6D67007E1867 /* Collection+OWS.swift */; }; - C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A2255B6D93007E1867 /* Identicon+ObjC.swift */; }; - C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */; }; - C38EF2A7255B6D93007E1867 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; }; C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */; }; C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */; }; C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.swift */; }; @@ -547,6 +544,9 @@ FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E728264937000CE219 /* MediaDetailViewController.swift */; }; FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E9282A1BB2000CE219 /* ThreadTypingIndicator.swift */; }; FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5EB282B8F17000CE219 /* AttachmentError.swift */; }; + FD16AB5B2A1DD7CA0083D849 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */; }; + FD16AB5F2A1DD98F0083D849 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; }; + FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */; }; FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */; }; FD17D79C27F40B2E00122BE0 /* SMKLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79B27F40B2E00122BE0 /* SMKLegacy.swift */; }; FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */; }; @@ -1489,9 +1489,8 @@ C38EF240255B6D67007E1867 /* UIView+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+OWS.swift"; path = "SignalUtilitiesKit/Utilities/UIView+OWS.swift"; sourceTree = SOURCE_ROOT; }; C38EF241255B6D67007E1867 /* Collection+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Collection+OWS.swift"; path = "SignalUtilitiesKit/Utilities/Collection+OWS.swift"; sourceTree = SOURCE_ROOT; }; C38EF281255B6D84007E1867 /* OWSAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSAudioSession.swift; path = SessionMessagingKit/Utilities/OWSAudioSession.swift; sourceTree = SOURCE_ROOT; }; - C38EF2A2255B6D93007E1867 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Identicon+ObjC.swift"; path = "SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift"; sourceTree = SOURCE_ROOT; }; - C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlaceholderIcon.swift; path = "SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift"; sourceTree = SOURCE_ROOT; }; - C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfilePictureView.swift; path = "SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift"; sourceTree = SOURCE_ROOT; }; + C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlaceholderIcon.swift; path = SessionUIKit/Components/PlaceholderIcon.swift; sourceTree = SOURCE_ROOT; }; + C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfilePictureView.swift; path = SessionUIKit/Components/ProfilePictureView.swift; sourceTree = SOURCE_ROOT; }; C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIViewController+Utilities.swift"; path = "SignalUtilitiesKit/Utilities/UIViewController+Utilities.swift"; sourceTree = SOURCE_ROOT; }; C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ScreenLock.swift; path = "SignalUtilitiesKit/Screen Lock/ScreenLock.swift"; sourceTree = SOURCE_ROOT; }; C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProximityMonitoringManager.swift; path = SessionMessagingKit/Utilities/ProximityMonitoringManager.swift; sourceTree = SOURCE_ROOT; }; @@ -1673,6 +1672,7 @@ FD09C5E728264937000CE219 /* MediaDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDetailViewController.swift; sourceTree = ""; }; FD09C5E9282A1BB2000CE219 /* ThreadTypingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTypingIndicator.swift; sourceTree = ""; }; FD09C5EB282B8F17000CE219 /* AttachmentError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentError.swift; sourceTree = ""; }; + FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfilePictureView+Convenience.swift"; sourceTree = ""; }; FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; FD17D79B27F40B2E00122BE0 /* SMKLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacy.swift; sourceTree = ""; }; @@ -2857,6 +2857,8 @@ C38EF3EE255B6DF6007E1867 /* GradientView.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, FD52090628B49738006098F6 /* ConfirmationModal.swift */, + C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */, + C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */, FD71165A28E6DDBC00B47552 /* StyledNavigationController.swift */, ); path = Components; @@ -2867,7 +2869,7 @@ children = ( C33FD9B7255A54A300E217F9 /* Meta */, C36096ED25AD20FD008B62B2 /* Media Viewing & Editing */, - C36096EF25AD2268008B62B2 /* Profile Pictures */, + FD16AB5D2A1DD8E70083D849 /* Profile Pictures */, C36096EE25AD21BC008B62B2 /* Screen Lock */, C3851CD225624B060061EEB0 /* Shared Views */, C360970125AD22D3008B62B2 /* Shared View Controllers */, @@ -3046,16 +3048,6 @@ path = "Screen Lock"; sourceTree = ""; }; - C36096EF25AD2268008B62B2 /* Profile Pictures */ = { - isa = PBXGroup; - children = ( - C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */, - C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */, - C38EF2A2255B6D93007E1867 /* Identicon+ObjC.swift */, - ); - path = "Profile Pictures"; - sourceTree = ""; - }; C360970125AD22D3008B62B2 /* Shared View Controllers */ = { isa = PBXGroup; children = ( @@ -3174,6 +3166,7 @@ FDC4386827B4E6B700C60D73 /* String+Utlities.swift */, FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */, FD772899284AF1BD0018502F /* Sodium+Utilities.swift */, + FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */, C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */, C3ECBF7A257056B700EA7FCE /* Threading.swift */, ); @@ -3558,6 +3551,13 @@ path = Models; sourceTree = ""; }; + FD16AB5D2A1DD8E70083D849 /* Profile Pictures */ = { + isa = PBXGroup; + children = ( + ); + path = "Profile Pictures"; + sourceTree = ""; + }; FD17D79427F3E03300122BE0 /* Migrations */ = { isa = PBXGroup; children = ( @@ -5171,6 +5171,7 @@ C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */, FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */, C331FFE02558FB0000070591 /* SearchBar.swift in Sources */, + FD16AB5B2A1DD7CA0083D849 /* PlaceholderIcon.swift in Sources */, FD71162C28E1451400B47552 /* Position.swift in Sources */, FD52090328B4680F006098F6 /* RadioButton.swift in Sources */, C331FFE82558FB0000070591 /* TextView.swift in Sources */, @@ -5181,6 +5182,7 @@ FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */, C331FF9A2558FA6B00070591 /* Values.swift in Sources */, FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */, + FD16AB5F2A1DD98F0083D849 /* ProfilePictureView.swift in Sources */, C331FFE42558FB0000070591 /* SessionButton.swift in Sources */, C331FFE92558FB0000070591 /* Separator.swift in Sources */, FD71163228E2C42A00B47552 /* IconSize.swift in Sources */, @@ -5199,7 +5201,6 @@ C38EF247255B6D67007E1867 /* NSAttributedString+OWS.m in Sources */, C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */, C38EF3C5255B6DE7007E1867 /* OWSViewController+ImageEditor.swift in Sources */, - C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */, C38EF385255B6DD2007E1867 /* AttachmentTextToolbar.swift in Sources */, C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */, FD71161E28D9772700B47552 /* UIViewController+OWS.swift in Sources */, @@ -5220,12 +5221,10 @@ C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */, C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */, C38EF388255B6DD2007E1867 /* AttachmentApprovalViewController.swift in Sources */, - C38EF2A7255B6D93007E1867 /* ProfilePictureView.swift in Sources */, C38EF22C255B6D5D007E1867 /* OWSVideoPlayer.swift in Sources */, C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */, C38EF407255B6DF7007E1867 /* Toast.swift in Sources */, C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */, - C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */, C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */, C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */, C38EF3C7255B6DE7007E1867 /* ImageEditorCanvasView.swift in Sources */, @@ -5558,6 +5557,7 @@ C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */, + FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */, FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */, B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index de3f64f48..f174346c6 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -5,6 +5,7 @@ import CallKit import GRDB import WebRTC import PromiseKit +import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit @@ -154,7 +155,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { self.contactName = Profile.displayName(db, id: sessionId, threadVariant: .contact) self.profilePicture = ProfileManager.profileAvatar(db, id: sessionId) .map { UIImage(data: $0) } - .defaulting(to: Identicon.generatePlaceholderIcon(seed: sessionId, text: self.contactName, size: 300)) + .defaulting(to: PlaceholderIcon.generate(seed: sessionId, text: self.contactName, size: 300)) WebRTCSession.current = self.webRTCSession self.webRTCSession.delegate = self diff --git a/Session/Media Viewing & Editing/CropScaleImageViewController.swift b/Session/Media Viewing & Editing/CropScaleImageViewController.swift index f6ad5d8ff..4cf5f7da1 100644 --- a/Session/Media Viewing & Editing/CropScaleImageViewController.swift +++ b/Session/Media Viewing & Editing/CropScaleImageViewController.swift @@ -32,7 +32,7 @@ import SignalUtilitiesKit let srcImage: UIImage - let successCompletion: ((UIImage) -> Void) + let successCompletion: ((Data) -> Void) var imageView: UIView! @@ -79,7 +79,7 @@ import SignalUtilitiesKit notImplemented() } - @objc required init(srcImage: UIImage, successCompletion : @escaping (UIImage) -> Void) { + @objc required init(srcImage: UIImage, successCompletion : @escaping (Data) -> Void) { // normalized() can be slightly expensive but in practice this is fine. self.srcImage = srcImage.normalized() self.successCompletion = successCompletion @@ -487,10 +487,9 @@ import SignalUtilitiesKit @objc func donePressed(sender: UIButton) { let successCompletion = self.successCompletion dismiss(animated: true, completion: { - guard let dstImage = self.generateDstImage() else { - return - } - successCompletion(dstImage) + guard let dstImageData: Data = self.generateDstImageData() else { return } + + successCompletion(dstImageData) }) } @@ -517,4 +516,8 @@ import SignalUtilitiesKit UIGraphicsEndImageContext() return scaledImage } + + func generateDstImageData() -> Data? { + return generateDstImage().map { $0.pngData() } + } } diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 1d590e0d3..35366179c 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index cf97bdb51..b7a7bbc93 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index ee4801866..e5b1cf5d4 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 4b1b87715..3c002daa7 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index a75e1070c..ef8fac68e 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index f23971351..086da6b13 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Êtes-vous sûr de vouloir supprimer votre conversation avec %@ ?"; "delete_conversation_confirmation_alert_title" = "Supprimer conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index ca058e22a..515ec2808 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index e6cd0d2a3..1f41192b4 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index c620b6ae6..9342b0e4c 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 2c1b9996f..48353cfb5 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index cc28d1898..5ee36faf0 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 477156edf..8a864919c 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 69e08221d..b1e3863aa 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 02587c0a2..345c6ed5b 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index d3f3b00b7..175222c59 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index c33b738d8..aeaaafb52 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 4f924c6fe..5886328d6 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 2fa76cecb..00f1ebd2f 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 6aae143ff..a0bbeb02e 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index a146cdbf7..3a2fdaa77 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 8725b7d5f..68c0bfba5 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index d4f9a737b..f537301d9 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index 509adaee6..3278a099e 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -52,7 +52,7 @@ class SettingsViewModel: SessionTableViewModel ())? = nil @@ -37,22 +36,7 @@ public class ConfirmationModal: Modal { return result }() - private lazy var imageViewContainer: UIView = { - let result: UIView = UIView() - result.isHidden = true - - return result - }() - - private lazy var imageView: UIImageView = { - let result: UIImageView = UIImageView() - result.clipsToBounds = true - result.contentMode = .scaleAspectFill - result.set(.width, to: ConfirmationModal.imageSize) - result.set(.height, to: ConfirmationModal.imageSize) - - return result - }() + private lazy var profileView: ProfilePictureView = ProfilePictureView(size: .hero) private lazy var confirmButton: UIButton = { let result: UIButton = Modal.createButton( @@ -73,7 +57,7 @@ public class ConfirmationModal: Modal { }() private lazy var contentStackView: UIStackView = { - let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, imageViewContainer ]) + let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, profileView ]) result.axis = .vertical result.spacing = Values.smallSpacing result.isLayoutMarginsRelativeArrangement = true @@ -141,11 +125,6 @@ public class ConfirmationModal: Modal { contentView.addSubview(mainStackView) contentView.addSubview(closeButton) - imageViewContainer.addSubview(imageView) - imageView.center(.horizontal, in: imageViewContainer) - imageView.pin(.top, to: .top, of: imageViewContainer, withInset: 15) - imageView.pin(.bottom, to: .bottom, of: imageViewContainer, withInset: -15) - mainStackView.pin(to: contentView) closeButton.pin(.top, to: .top, of: contentView, withInset: 8) closeButton.pin(.right, to: .right, of: contentView, withInset: -8) @@ -185,14 +164,16 @@ public class ConfirmationModal: Modal { explanationLabel.attributedText = attributedText explanationLabel.isHidden = false - case .image(let placeholder, let value, let style, let onClick): + case .image(let placeholder, let value, let icon, let style, let onClick): mainStackView.spacing = 0 - imageView.image = (value ?? placeholder) - imageView.layer.cornerRadius = (style == .circular ? - (ConfirmationModal.imageSize / 2) : - 0 + profileView.clipsToBounds = (style == .circular) + profileView.update( + ProfilePictureView.Info( + imageData: (value ?? placeholder), + icon: icon + ) ) - imageViewContainer.isHidden = false + profileView.isHidden = false internalOnBodyTap = onClick } @@ -406,8 +387,9 @@ public extension ConfirmationModal.Info { // case input(placeholder: String, value: String?) // case radio(explanation: NSAttributedString?, options: [(title: String, selected: Bool)]) case image( - placeholder: UIImage?, - value: UIImage?, + placeholderData: Data?, + valueData: Data?, + icon: ProfilePictureView.ProfileIcon = .none, style: ImageStyle, onClick: (() -> ()) ) @@ -432,10 +414,11 @@ public extension ConfirmationModal.Info { // lhsOptions.map { "\($0.0)-\($0.1)" } == rhsValue.map { "\($0.0)-\($0.1)" } // ) - case (.image(let lhsPlaceholder, let lhsValue, let lhsStyle, _), .image(let rhsPlaceholder, let rhsValue, let rhsStyle, _)): + case (.image(let lhsPlaceholder, let lhsValue, let lhsIcon, let lhsStyle, _), .image(let rhsPlaceholder, let rhsValue, let rhsIcon, let rhsStyle, _)): return ( lhsPlaceholder == rhsPlaceholder && lhsValue == rhsValue && + lhsIcon == rhsIcon && lhsStyle == rhsStyle ) @@ -449,9 +432,10 @@ public extension ConfirmationModal.Info { case .text(let text): text.hash(into: &hasher) case .attributedText(let text): text.hash(into: &hasher) - case .image(let placeholder, let value, let style, _): + case .image(let placeholder, let value, let icon, let style, _): placeholder.hash(into: &hasher) value.hash(into: &hasher) + icon.hash(into: &hasher) style.hash(into: &hasher) } } diff --git a/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift b/SessionUIKit/Components/PlaceholderIcon.swift similarity index 54% rename from SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift rename to SessionUIKit/Components/PlaceholderIcon.swift index 33246ebce..7b6e00e92 100644 --- a/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift +++ b/SessionUIKit/Components/PlaceholderIcon.swift @@ -2,14 +2,23 @@ import UIKit import CryptoSwift -import SessionUIKit +import SessionUtilitiesKit public class PlaceholderIcon { + private static let placeholderCache: Atomic> = { + let result = NSCache() + result.countLimit = 50 + + return Atomic(result) + }() + private let seed: Int // Colour palette private var colors: [UIColor] = Theme.PrimaryColor.allCases.map { $0.color } + // MARK: - Initialization + init(seed: Int, colors: [UIColor]? = nil) { self.seed = seed if let colors = colors { self.colors = colors } @@ -21,7 +30,7 @@ public class PlaceholderIcon { if (hash.matches("^[0-9A-Fa-f]+$") && hash.count >= 12) { hash = seed.sha512() } guard let number = Int(hash.substring(to: 12), radix: 16) else { - owsFailDebug("Failed to generate number from seed string: \(seed).") + SNLog("Failed to generate number from seed string: \(seed).") self.init(seed: 0, colors: colors) return } @@ -29,7 +38,53 @@ public class PlaceholderIcon { self.init(seed: number, colors: colors) } - public func generateLayer(with diameter: CGFloat, text: String) -> CALayer { + // MARK: - Convenience + + public static func generate(seed: String, text: String, size: CGFloat) -> UIImage { + let icon = PlaceholderIcon(seed: seed) + + var content: String = (text.hasSuffix("\(String(seed.suffix(4))))") ? + (text.split(separator: "(") + .first + .map { String($0) }) + .defaulting(to: text) : + text + ) + + if content.count > 2 && SessionId.Prefix(from: content) != nil { + content.removeFirst(2) + } + + let initials: String = content + .split(separator: " ") + .compactMap { word in word.first.map { String($0) } } + .joined() + let cacheKey: String = "\(content)-\(Int(floor(size)))" + + if let cachedIcon: UIImage = placeholderCache.wrappedValue.object(forKey: cacheKey as NSString) { + return cachedIcon + } + + let layer = icon.generateLayer( + with: size, + text: (initials.count >= 2 ? + initials.substring(to: 2).uppercased() : + content.substring(to: 2).uppercased() + ) + ) + + let rect = CGRect(origin: CGPoint.zero, size: layer.frame.size) + let renderer = UIGraphicsImageRenderer(size: rect.size) + let result = renderer.image { layer.render(in: $0.cgContext) } + + placeholderCache.mutate { $0.setObject(result, forKey: cacheKey as NSString) } + + return result + } + + // MARK: - Internal + + private func generateLayer(with diameter: CGFloat, text: String) -> CALayer { let color: UIColor = self.colors[seed % self.colors.count] let base: CALayer = getTextLayer(with: diameter, color: color, text: text) base.masksToBounds = true diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SessionUIKit/Components/ProfilePictureView.swift similarity index 55% rename from SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift rename to SessionUIKit/Components/ProfilePictureView.swift index 79f973472..60703d805 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SessionUIKit/Components/ProfilePictureView.swift @@ -3,10 +3,36 @@ import UIKit import GRDB import YYImage -import SessionUIKit -import SessionMessagingKit public final class ProfilePictureView: UIView { + public struct Info { + let imageData: Data? + let renderingMode: UIImage.RenderingMode + let themeTintColor: ThemeValue? + let inset: UIEdgeInsets + let icon: ProfileIcon + let backgroundColor: ThemeValue? + let forcedBackgroundColor: ForcedThemeValue? + + public init( + imageData: Data?, + renderingMode: UIImage.RenderingMode = .automatic, + themeTintColor: ThemeValue? = nil, + inset: UIEdgeInsets = .zero, + icon: ProfileIcon = .none, + backgroundColor: ThemeValue? = nil, + forcedBackgroundColor: ForcedThemeValue? = nil + ) { + self.imageData = imageData + self.renderingMode = renderingMode + self.themeTintColor = themeTintColor + self.inset = inset + self.icon = icon + self.backgroundColor = backgroundColor + self.forcedBackgroundColor = forcedBackgroundColor + } + } + public enum Size { case navigation case message @@ -21,7 +47,7 @@ public final class ProfilePictureView: UIView { } } - var imageSize: CGFloat { + public var imageSize: CGFloat { switch self { case .navigation, .message: return 26 case .list: return 46 @@ -29,7 +55,7 @@ public final class ProfilePictureView: UIView { } } - var multiImageSize: CGFloat { + public var multiImageSize: CGFloat { switch self { case .navigation, .message: return 18 // Shouldn't be used case .list: return 32 @@ -44,32 +70,31 @@ public final class ProfilePictureView: UIView { 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 { + public enum ProfileIcon: Equatable, Hashable { case none case crown case rightPlus + + func iconVerticalInset(for size: Size) -> CGFloat { + switch (self, size) { + case (.crown, .navigation), (.crown, .message): return 1 + case (.crown, .list): return 3 + case (.crown, .hero): return 5 + + case (.rightPlus, _): return 3 + default: return 0 + } + } } 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 @@ -82,7 +107,18 @@ public final class ProfilePictureView: UIView { self.widthConstraint.constant = (customWidth ?? self.size.viewSize) } } - private var hasTappableProfilePicture: Bool = false + override public var clipsToBounds: Bool { + didSet { + imageContainerView.clipsToBounds = clipsToBounds + additionalImageContainerView.clipsToBounds = clipsToBounds + + imageContainerView.layer.cornerRadius = (clipsToBounds ? + (additionalImageContainerView.isHidden ? (size.imageSize / 2) : (size.multiImageSize / 2)) : + 0 + ) + imageContainerView.layer.cornerRadius = (clipsToBounds ? (size.multiImageSize / 2) : 0) + } + } // MARK: - Constraints @@ -108,6 +144,26 @@ public final class ProfilePictureView: UIView { private var additionalProfileIconBackgroundRightAlignConstraint: NSLayoutConstraint! private var additionalProfileIconBackgroundWidthConstraint: NSLayoutConstraint! private var additionalProfileIconBackgroundHeightConstraint: NSLayoutConstraint! + private lazy var imageEdgeConstraints: [NSLayoutConstraint] = [ // MUST be in 'top, left, bottom, right' order + imageView.pin(.top, to: .top, of: imageContainerView, withInset: 0), + imageView.pin(.left, to: .left, of: imageContainerView, withInset: 0), + imageView.pin(.bottom, to: .bottom, of: imageContainerView, withInset: 0), + imageView.pin(.right, to: .right, of: imageContainerView, withInset: 0), + animatedImageView.pin(.top, to: .top, of: imageContainerView, withInset: 0), + animatedImageView.pin(.left, to: .left, of: imageContainerView, withInset: 0), + animatedImageView.pin(.bottom, to: .bottom, of: imageContainerView, withInset: 0), + animatedImageView.pin(.right, to: .right, of: imageContainerView, withInset: 0) + ] + private lazy var additionalImageEdgeConstraints: [NSLayoutConstraint] = [ // MUST be in 'top, left, bottom, right' order + additionalImageView.pin(.top, to: .top, of: additionalImageContainerView, withInset: 0), + additionalImageView.pin(.left, to: .left, of: additionalImageContainerView, withInset: 0), + additionalImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 0), + additionalImageView.pin(.right, to: .right, of: additionalImageContainerView, withInset: 0), + additionalAnimatedImageView.pin(.top, to: .top, of: additionalImageContainerView, withInset: 0), + additionalAnimatedImageView.pin(.left, to: .left, of: additionalImageContainerView, withInset: 0), + additionalAnimatedImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 0), + additionalAnimatedImageView.pin(.right, to: .right, of: additionalImageContainerView, withInset: 0) + ] // MARK: - Components @@ -144,18 +200,7 @@ public final class ProfilePictureView: UIView { result.clipsToBounds = true result.themeBackgroundColor = .primary result.themeBorderColor = .backgroundPrimary - result.isHidden = true - - return result - }() - - private lazy var additionalProfilePlaceholderImageView: UIImageView = { - let result: UIImageView = UIImageView( - image: UIImage(systemName: "person.fill")?.withRenderingMode(.alwaysTemplate) - ) - result.translatesAutoresizingMaskIntoConstraints = false - result.contentMode = .scaleAspectFill - result.themeTintColor = .textPrimary + result.layer.borderWidth = 1 result.isHidden = true return result @@ -215,6 +260,7 @@ public final class ProfilePictureView: UIView { super.init(frame: CGRect(x: 0, y: 0, width: size.viewSize, height: size.viewSize)) + clipsToBounds = true setUpViewHierarchy() } @@ -251,23 +297,16 @@ public final class ProfilePictureView: UIView { imageContainerView.addSubview(animatedImageView) additionalImageContainerView.addSubview(additionalImageView) additionalImageContainerView.addSubview(additionalAnimatedImageView) - additionalImageContainerView.addSubview(additionalProfilePlaceholderImageView) - imageView.pin(to: imageContainerView) - animatedImageView.pin(to: imageContainerView) - additionalImageView.pin(to: additionalImageContainerView) - additionalAnimatedImageView.pin(to: additionalImageContainerView) - - additionalProfilePlaceholderImageView.pin(.top, to: .top, of: additionalImageContainerView, withInset: 3) - additionalProfilePlaceholderImageView.pin(.left, to: .left, of: additionalImageContainerView) - additionalProfilePlaceholderImageView.pin(.right, to: .right, of: additionalImageContainerView) - additionalProfilePlaceholderImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 5) + // Activate the image edge constraints + imageEdgeConstraints.forEach { $0.isActive = true } + additionalImageEdgeConstraints.forEach { $0.isActive = true } profileIconTopConstraint = profileIconImageView.pin( .top, to: .top, of: profileIconBackgroundView, - withInset: size.iconVerticalInset + withInset: 0 ) profileIconImageView.pin(.left, to: .left, of: profileIconBackgroundView) profileIconImageView.pin(.right, to: .right, of: profileIconBackgroundView) @@ -275,7 +314,7 @@ public final class ProfilePictureView: UIView { .bottom, to: .bottom, of: profileIconBackgroundView, - withInset: -size.iconVerticalInset + withInset: 0 ) profileIconBackgroundLeftAlignConstraint = profileIconBackgroundView.pin(.leading, to: .leading, of: imageContainerView) profileIconBackgroundRightAlignConstraint = profileIconBackgroundView.pin(.trailing, to: .trailing, of: imageContainerView) @@ -289,7 +328,7 @@ public final class ProfilePictureView: UIView { .top, to: .top, of: additionalProfileIconBackgroundView, - withInset: size.iconVerticalInset + withInset: 0 ) additionalProfileIconImageView.pin(.left, to: .left, of: additionalProfileIconBackgroundView) additionalProfileIconImageView.pin(.right, to: .right, of: additionalProfileIconBackgroundView) @@ -297,7 +336,7 @@ public final class ProfilePictureView: UIView { .bottom, to: .bottom, of: additionalProfileIconBackgroundView, - withInset: -size.iconVerticalInset + withInset: 0 ) additionalProfileIconBackgroundLeftAlignConstraint = additionalProfileIconBackgroundView.pin(.leading, to: .leading, of: additionalImageContainerView) additionalProfileIconBackgroundRightAlignConstraint = additionalProfileIconBackgroundView.pin(.trailing, to: .trailing, of: additionalImageContainerView) @@ -314,8 +353,10 @@ public final class ProfilePictureView: UIView { icon: ProfileIcon, imageView: UIImageView, backgroundView: UIView, + topConstraint: NSLayoutConstraint, leftAlignConstraint: NSLayoutConstraint, - rightAlignConstraint: NSLayoutConstraint + rightAlignConstraint: NSLayoutConstraint, + bottomConstraint: NSLayoutConstraint ) { backgroundView.isHidden = (icon == .none) leftAlignConstraint.isActive = ( @@ -325,6 +366,8 @@ public final class ProfilePictureView: UIView { rightAlignConstraint.isActive = ( icon == .rightPlus ) + topConstraint.constant = icon.iconVerticalInset(for: size) + bottomConstraint.constant = -icon.iconVerticalInset(for: size) switch icon { case .none: imageView.image = nil @@ -345,201 +388,159 @@ public final class ProfilePictureView: UIView { } case .rightPlus: - imageView.image = UIImage(systemName: "plus") + imageView.image = UIImage( + systemName: "plus", + withConfiguration: UIImage.SymbolConfiguration(weight: .semibold) + ) imageView.themeTintColor = .black - backgroundView.themeBackgroundColor = .primary + backgroundView.themeBackgroundColorForced = .primary(.green) } } - - 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() + + // MARK: - Content + + private func prepareForReuse() { + imageView.contentMode = .scaleAspectFill + imageView.isHidden = true + animatedImageView.contentMode = .scaleAspectFill + animatedImageView.isHidden = true + imageContainerView.clipsToBounds = clipsToBounds + imageContainerView.themeBackgroundColor = .backgroundSecondary + additionalImageContainerView.isHidden = true + animatedImageView.image = nil + additionalImageView.image = nil + additionalAnimatedImageView.image = nil + additionalImageView.isHidden = true + additionalAnimatedImageView.isHidden = true + additionalImageContainerView.clipsToBounds = clipsToBounds - // Sort out the profile icon first + imageViewTopConstraint.isActive = false + imageViewLeadingConstraint.isActive = false + imageViewCenterXConstraint.isActive = true + imageViewCenterYConstraint.isActive = true + profileIconBackgroundView.isHidden = true + profileIconBackgroundLeftAlignConstraint.isActive = false + profileIconBackgroundRightAlignConstraint.isActive = false + additionalProfileIconBackgroundView.isHidden = true + additionalProfileIconBackgroundLeftAlignConstraint.isActive = false + additionalProfileIconBackgroundRightAlignConstraint.isActive = false + imageEdgeConstraints.forEach { $0.constant = 0 } + additionalImageEdgeConstraints.forEach { $0.constant = 0 } + } + + public func update( + _ info: Info, + additionalInfo: Info? = nil + ) { + prepareForReuse() + + // Sort out the icon first updateIconView( - icon: icon, + icon: info.icon, imageView: profileIconImageView, backgroundView: profileIconBackgroundView, + topConstraint: profileIconTopConstraint, leftAlignConstraint: profileIconBackgroundLeftAlignConstraint, - rightAlignConstraint: profileIconBackgroundRightAlignConstraint + rightAlignConstraint: profileIconBackgroundRightAlignConstraint, + bottomConstraint: profileIconBottomConstraint ) - guard !useFallbackPicture else { - switch self.size { - case .navigation, .message: imageView.image = #imageLiteral(resourceName: "SessionWhite16") - case .list: imageView.image = #imageLiteral(resourceName: "SessionWhite24") - case .hero: imageView.image = #imageLiteral(resourceName: "SessionWhite40") + // Populate the main imageView + switch info.imageData?.guessedImageFormat { + case .gif, .webp: animatedImageView.image = info.imageData.map { YYImage(data: $0) } + default: + imageView.image = info.imageData + .map { + guard info.renderingMode != .automatic else { return UIImage(data: $0) } + + return UIImage(data: $0)?.withRenderingMode(info.renderingMode) + } + } + + imageView.themeTintColor = info.themeTintColor + imageView.isHidden = (imageView.image == nil) + animatedImageView.themeTintColor = info.themeTintColor + animatedImageView.isHidden = (animatedImageView.image == nil) + imageContainerView.themeBackgroundColor = info.backgroundColor + imageContainerView.themeBackgroundColorForced = info.forcedBackgroundColor + profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2) + imageEdgeConstraints.enumerated().forEach { index, constraint in + switch index % 4 { + case 0: constraint.constant = info.inset.top + case 1: constraint.constant = info.inset.left + case 2: constraint.constant = -info.inset.bottom + case 3: constraint.constant = -info.inset.right + default: break } - - imageView.contentMode = .center - imageView.isHidden = false - animatedImageView.isHidden = true - imageContainerView.themeBackgroundColorForced = .theme(.classicDark, color: .borderSeparator) - 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 - additionalAnimatedImageView.image = nil - additionalImageView.isHidden = true - additionalAnimatedImageView.isHidden = true - additionalProfilePlaceholderImageView.isHidden = true + } + + // Check if there is a second image (if not then set the size and finish) + guard let additionalInfo: Info = additionalInfo else { + imageViewWidthConstraint.constant = size.imageSize + imageViewHeightConstraint.constant = size.imageSize + imageContainerView.layer.cornerRadius = (imageContainerView.clipsToBounds ? (size.imageSize / 2) : 0) return } - guard !publicKey.isEmpty || openGroupProfilePictureData != nil else { return } - func getProfilePicture(of size: CGFloat, for publicKey: String, profile: Profile?) -> (image: UIImage?, animatedImage: YYImage?, isTappable: Bool) { - if let profile: Profile = profile, let profileData: Data = ProfileManager.profileAvatar(profile: profile) { - let format: ImageFormat = profileData.guessedImageFormat - - let image: UIImage? = (format == .gif || format == .webp ? - nil : - UIImage(data: profileData) - ) - let animatedImage: YYImage? = (format != .gif && format != .webp ? - nil : - YYImage(data: profileData) - ) - - if image != nil || animatedImage != nil { - return (image, animatedImage, true) - } - } - - return ( - Identicon.generatePlaceholderIcon( - seed: publicKey, - text: (profile?.displayName(for: threadVariant)) - .defaulting(to: publicKey), - size: size - ), - nil, - false - ) - } + // Sort out the additional icon first + updateIconView( + icon: additionalInfo.icon, + imageView: additionalProfileIconImageView, + backgroundView: additionalProfileIconBackgroundView, + topConstraint: additionalProfileIconTopConstraint, + leftAlignConstraint: additionalProfileIconBackgroundLeftAlignConstraint, + rightAlignConstraint: additionalProfileIconBackgroundRightAlignConstraint, + bottomConstraint: additionalProfileIconBottomConstraint + ) - // Calulate the sizes (and set the additional image content) - let targetSize: CGFloat - - switch (threadVariant, showMultiAvatarForClosedGroup) { - case (.closedGroup, true): - 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: 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 - additionalAnimatedImageView.image = animatedImage - additionalImageView.isHidden = (animatedImage != nil) - additionalAnimatedImageView.isHidden = (animatedImage == nil) - additionalProfilePlaceholderImageView.isHidden = true - } - else { - additionalImageView.isHidden = true - additionalAnimatedImageView.isHidden = true - additionalProfilePlaceholderImageView.isHidden = false - } - + // Set the additional image content and reposition the image views correctly + switch additionalInfo.imageData?.guessedImageFormat { + case .gif, .webp: additionalAnimatedImageView.image = additionalInfo.imageData.map { YYImage(data: $0) } default: - 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 + additionalImageView.image = additionalInfo.imageData + .map { + guard additionalInfo.renderingMode != .automatic else { return UIImage(data: $0) } + + return UIImage(data: $0)?.withRenderingMode(additionalInfo.renderingMode) + } } - // Set the image - if let openGroupProfilePictureData: Data = openGroupProfilePictureData { - let format: ImageFormat = openGroupProfilePictureData.guessedImageFormat - - let image: UIImage? = (format == .gif || format == .webp ? - nil : - UIImage(data: openGroupProfilePictureData) - ) - let animatedImage: YYImage? = (format != .gif && format != .webp ? - nil : - YYImage(data: openGroupProfilePictureData) - ) - - imageView.image = image - animatedImageView.image = animatedImage - imageView.isHidden = (animatedImage != nil) - animatedImageView.isHidden = (animatedImage == nil) - hasTappableProfilePicture = true - } - else { - let (image, animatedImage, isTappable): (UIImage?, YYImage?, Bool) = getProfilePicture( - of: targetSize, - for: publicKey, - profile: profile - ) - imageView.image = image - animatedImageView.image = animatedImage - imageView.isHidden = (animatedImage != nil) - animatedImageView.isHidden = (animatedImage == nil) - hasTappableProfilePicture = isTappable + additionalImageView.themeTintColor = additionalInfo.themeTintColor + additionalImageView.isHidden = (additionalImageView.image == nil) + additionalAnimatedImageView.themeTintColor = additionalInfo.themeTintColor + additionalAnimatedImageView.isHidden = (additionalAnimatedImageView.image == nil) + additionalImageContainerView.isHidden = false + + switch (info.backgroundColor, info.forcedBackgroundColor) { + case (_, .some(let color)): additionalImageContainerView.themeBackgroundColorForced = color + case (.some(let color), _): additionalImageContainerView.themeBackgroundColor = color + default: additionalImageContainerView.themeBackgroundColor = .primary } - 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) + additionalImageEdgeConstraints.enumerated().forEach { index, constraint in + switch index % 4 { + case 0: constraint.constant = additionalInfo.inset.top + case 1: constraint.constant = additionalInfo.inset.left + case 2: constraint.constant = -additionalInfo.inset.bottom + case 3: constraint.constant = -additionalInfo.inset.right + default: break + } + } + + imageViewTopConstraint.isActive = true + imageViewLeadingConstraint.isActive = true + imageViewCenterXConstraint.isActive = false + imageViewCenterYConstraint.isActive = false + + imageViewWidthConstraint.constant = size.multiImageSize + imageViewHeightConstraint.constant = size.multiImageSize + imageContainerView.layer.cornerRadius = (imageContainerView.clipsToBounds ? (size.multiImageSize / 2) : 0) + additionalImageViewWidthConstraint.constant = size.multiImageSize + additionalImageViewHeightConstraint.constant = size.multiImageSize + additionalImageContainerView.layer.cornerRadius = (additionalImageContainerView.clipsToBounds ? + (size.multiImageSize / 2) : + 0 + ) additionalProfileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2) } - - // MARK: - Convenience - - @objc public func getProfilePicture() -> UIImage? { - return (hasTappableProfilePicture ? imageView.image : nil) - } } diff --git a/SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift b/SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift deleted file mode 100644 index 256438529..000000000 --- a/SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import UIKit -import SessionUIKit -import SessionUtilitiesKit - -@objc(LKIdenticon) -public final class Identicon: NSObject { - private static let placeholderCache: Atomic> = { - let result = NSCache() - result.countLimit = 50 - - return Atomic(result) - }() - - @objc public static func generatePlaceholderIcon(seed: String, text: String, size: CGFloat) -> UIImage { - let icon = PlaceholderIcon(seed: seed) - - var content: String = (text.hasSuffix("\(String(seed.suffix(4))))") ? - (text.split(separator: "(") - .first - .map { String($0) }) - .defaulting(to: text) : - text - ) - - if content.count > 2 && SessionId.Prefix(from: content) != nil { - content.removeFirst(2) - } - - let initials: String = content - .split(separator: " ") - .compactMap { word in word.first.map { String($0) } } - .joined() - let cacheKey: String = "\(content)-\(Int(floor(size)))" - - if let cachedIcon: UIImage = placeholderCache.wrappedValue.object(forKey: cacheKey as NSString) { - return cachedIcon - } - - let layer = icon.generateLayer( - with: size, - text: (initials.count >= 2 ? - initials.substring(to: 2).uppercased() : - content.substring(to: 2).uppercased() - ) - ) - - let rect = CGRect(origin: CGPoint.zero, size: layer.frame.size) - let renderer = UIGraphicsImageRenderer(size: rect.size) - let result = renderer.image { layer.render(in: $0.cgContext) } - - placeholderCache.mutate { $0.setObject(result, forKey: cacheKey as NSString) } - - return result - } -} From 22303f245858c5aaddbf4a7a0dbc7626357f54a9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 25 May 2023 08:42:27 +1000 Subject: [PATCH 3/3] Colour tweak --- SessionUIKit/Components/ProfilePictureView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionUIKit/Components/ProfilePictureView.swift b/SessionUIKit/Components/ProfilePictureView.swift index 60703d805..e8b9350a6 100644 --- a/SessionUIKit/Components/ProfilePictureView.swift +++ b/SessionUIKit/Components/ProfilePictureView.swift @@ -393,7 +393,7 @@ public final class ProfilePictureView: UIView { withConfiguration: UIImage.SymbolConfiguration(weight: .semibold) ) imageView.themeTintColor = .black - backgroundView.themeBackgroundColorForced = .primary(.green) + backgroundView.themeBackgroundColor = .primary } }