From 46b688dd35904376e49d567a49e740e560a313bd Mon Sep 17 00:00:00 2001 From: Michelle Linington Date: Fri, 12 Nov 2021 19:00:24 -0800 Subject: [PATCH] Further updates to avatar badges: - Design speced out explicit offsets for 24pt and 112pt avatars - We won't show badges in call views Also fixes a couple bugs: - Incorrect interpolation of interstitial badge sizes - We should badge conversation headers and typing indicators --- .../Group/GroupCallMemberSheet.swift | 5 ++- .../Group/GroupCallMemberView.swift | 2 +- .../IndividualCallViewController.swift | 5 ++- .../AppSettingsViewController.swift | 2 +- .../CV/CVComponentState.swift | 2 +- .../CVComponentThreadDetails.swift | 4 +-- .../ConversationHeaderView.swift | 2 +- .../ReturnToCallViewController.swift | 5 ++- .../src/views/NameCollisionReviewCell.swift | 2 +- SignalUI/Views/ConversationAvatarView.swift | 34 ++++++++++++++----- 10 files changed, 44 insertions(+), 19 deletions(-) diff --git a/Signal/src/Calls/UserInterface/Group/GroupCallMemberSheet.swift b/Signal/src/Calls/UserInterface/Group/GroupCallMemberSheet.swift index c4470d8284..4e453df21b 100644 --- a/Signal/src/Calls/UserInterface/Group/GroupCallMemberSheet.swift +++ b/Signal/src/Calls/UserInterface/Group/GroupCallMemberSheet.swift @@ -234,7 +234,10 @@ extension GroupCallMemberSheet: CallObserver { private class GroupCallMemberCell: UITableViewCell { static let reuseIdentifier = "GroupCallMemberCell" - let avatarView = ConversationAvatarView(sizeClass: .thirtySix, localUserDisplayMode: .asUser) + let avatarView = ConversationAvatarView( + sizeClass: .thirtySix, + localUserDisplayMode: .asUser, + badged: false) let nameLabel = UILabel() let videoMutedIndicator = UIImageView() let audioMutedIndicator = UIImageView() diff --git a/Signal/src/Calls/UserInterface/Group/GroupCallMemberView.swift b/Signal/src/Calls/UserInterface/Group/GroupCallMemberView.swift index 4f5f7942b9..73438fe324 100644 --- a/Signal/src/Calls/UserInterface/Group/GroupCallMemberView.swift +++ b/Signal/src/Calls/UserInterface/Group/GroupCallMemberView.swift @@ -254,7 +254,7 @@ class GroupCallRemoteMemberView: GroupCallMemberView { var deferredReconfigTimer: Timer? let errorView = GroupCallErrorView() let spinner = UIActivityIndicatorView(style: .whiteLarge) - let avatarView = ConversationAvatarView(localUserDisplayMode: .asUser) + let avatarView = ConversationAvatarView(localUserDisplayMode: .asUser, badged: false) var isCallMinimized: Bool = false { didSet { diff --git a/Signal/src/Calls/UserInterface/Individual/IndividualCallViewController.swift b/Signal/src/Calls/UserInterface/Individual/IndividualCallViewController.swift index 0026bed3a7..859ff52d10 100644 --- a/Signal/src/Calls/UserInterface/Individual/IndividualCallViewController.swift +++ b/Signal/src/Calls/UserInterface/Individual/IndividualCallViewController.swift @@ -67,7 +67,10 @@ class IndividualCallViewController: OWSViewController, CallObserver, CallAudioSe // MARK: - Contact Views private lazy var contactNameLabel = MarqueeLabel() - private lazy var contactAvatarView = ConversationAvatarView(sizeClass: .customDiameter(200), localUserDisplayMode: .asUser) + private lazy var contactAvatarView = ConversationAvatarView( + sizeClass: .customDiameter(200), + localUserDisplayMode: .asUser, + badged: false) private lazy var contactAvatarContainerView = UIView.container() private lazy var callStatusLabel = UILabel() private lazy var backButton = UIButton() diff --git a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.swift b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.swift index bb64698d8c..93fa3ac5a4 100644 --- a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.swift @@ -305,7 +305,7 @@ class AppSettingsViewController: OWSTableViewController2 { let snapshot = profileManagerImpl.localProfileSnapshot(shouldIncludeAvatar: false) let avatarImageView = ConversationAvatarView( - sizeClass: .customDiameter(64), + sizeClass: .sixtyFour, localUserDisplayMode: .asUser) if let localAddress = tsAccountManager.localAddress { diff --git a/Signal/src/ViewControllers/ConversationView/CV/CVComponentState.swift b/Signal/src/ViewControllers/ConversationView/CV/CVComponentState.swift index 0c658c5bf8..1240337b8d 100644 --- a/Signal/src/ViewControllers/ConversationView/CV/CVComponentState.swift +++ b/Signal/src/ViewControllers/ConversationView/CV/CVComponentState.swift @@ -646,7 +646,7 @@ fileprivate extension CVComponentState.Builder { } return self.avatarBuilder.buildAvatarDataSource( forAddress: typingIndicatorInteraction.address, - includingBadge: false, + includingBadge: true, localUserDisplayMode: .asUser, diameterPoints: UInt(ConversationStyle.groupMessageAvatarSizeClass.avatarDiameter)) }() diff --git a/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentThreadDetails.swift b/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentThreadDetails.swift index 57c2bed900..8736bf12be 100644 --- a/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentThreadDetails.swift +++ b/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentThreadDetails.swift @@ -220,7 +220,7 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent { textAlignment: .center) } - private static let avatarSizeClass = ConversationAvatarView.Configuration.SizeClass.customDiameter(112) + private static let avatarSizeClass = ConversationAvatarView.Configuration.SizeClass.oneHundredTwelve private var avatarSizeClass: ConversationAvatarView.Configuration.SizeClass { Self.avatarSizeClass } static func buildComponentState(thread: TSThread, @@ -252,7 +252,7 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent { avatarBuilder: CVAvatarBuilder) -> CVComponentState.ThreadDetails { let avatarDataSource = avatarBuilder.buildAvatarDataSource(forAddress: contactThread.contactAddress, - includingBadge: false, + includingBadge: true, localUserDisplayMode: .noteToSelf, diameterPoints: avatarSizeClass.avatarDiameter) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationHeaderView.swift b/Signal/src/ViewControllers/ConversationView/ConversationHeaderView.swift index ca1ca92059..45fc218a87 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationHeaderView.swift +++ b/Signal/src/ViewControllers/ConversationView/ConversationHeaderView.swift @@ -53,7 +53,7 @@ public class ConversationHeaderView: UIStackView { private let subtitleLabel: UILabel private var avatarSizeClass: ConversationAvatarView.Configuration.SizeClass { - traitCollection.verticalSizeClass == .compact ? .customDiameter(24) : .thirtySix + traitCollection.verticalSizeClass == .compact ? .twentyFour : .thirtySix } private lazy var avatarView = ConversationAvatarView( sizeClass: avatarSizeClass, diff --git a/Signal/src/ViewControllers/ReturnToCallViewController.swift b/Signal/src/ViewControllers/ReturnToCallViewController.swift index bdfd9ae568..f13383bc9b 100644 --- a/Signal/src/ViewControllers/ReturnToCallViewController.swift +++ b/Signal/src/ViewControllers/ReturnToCallViewController.swift @@ -75,7 +75,10 @@ public class ReturnToCallViewController: UIViewController { callViewController = nil } - private lazy var avatarView = ConversationAvatarView(sizeClass: .customDiameter(60), localUserDisplayMode: .asUser) + private lazy var avatarView = ConversationAvatarView( + sizeClass: .customDiameter(60), + localUserDisplayMode: .asUser, + badged: false) private lazy var backgroundAvatarView = UIImageView() private lazy var blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) diff --git a/Signal/src/views/NameCollisionReviewCell.swift b/Signal/src/views/NameCollisionReviewCell.swift index c1c0b5acb8..edf1b96bf9 100644 --- a/Signal/src/views/NameCollisionReviewCell.swift +++ b/Signal/src/views/NameCollisionReviewCell.swift @@ -89,7 +89,7 @@ extension NameCollision { class NameCollisionCell: UITableViewCell { typealias Action = (title: String, action: () -> Void) - let avatarView = ConversationAvatarView(sizeClass: .customDiameter(64), localUserDisplayMode: .asUser) + let avatarView = ConversationAvatarView(sizeClass: .sixtyFour, localUserDisplayMode: .asUser) let nameLabel: UILabel = { let label = UILabel() diff --git a/SignalUI/Views/ConversationAvatarView.swift b/SignalUI/Views/ConversationAvatarView.swift index 292864b669..0328d8ed90 100644 --- a/SignalUI/Views/ConversationAvatarView.swift +++ b/SignalUI/Views/ConversationAvatarView.swift @@ -38,6 +38,7 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView { public struct Configuration: Equatable { public enum SizeClass: Equatable { + case twentyFour case twentyEight case thirtySix case forty @@ -46,6 +47,7 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView { case sixtyFour case eighty case eightyEight + case oneHundredTwelve // Badge sprites may have artifacts from scaling to custom size classes // Stick to explicit size classes when you can @@ -53,6 +55,8 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView { public init(avatarDiameter: UInt) { switch avatarDiameter { + case Self.twentyFour.avatarDiameter: + self = .twentyFour case Self.twentyEight.avatarDiameter: self = .twentyEight case Self.thirtySix.avatarDiameter: @@ -69,6 +73,8 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView { self = .eighty case Self.eightyEight.avatarDiameter: self = .eightyEight + case Self.oneHundredTwelve.avatarDiameter: + self = .oneHundredTwelve default: self = .customDiameter(avatarDiameter) } @@ -526,6 +532,11 @@ public enum ConversationAvatarDataSource: Equatable, Dependencies, CustomStringC Logger.warn("Ignoring badge request. Badge flag currently disabled") return nil } + guard configuration.sizeClass.badgeDiameter >= 16 else { + // We never want to show a badge <= 16pts + Logger.warn("Skipping badge request for badge with diameter of \(configuration.sizeClass.badgeDiameter)") + return nil + } let targetAddress: SignalServiceAddress switch self { @@ -652,6 +663,7 @@ extension ConversationAvatarView.Configuration.SizeClass { public var avatarDiameter: UInt { switch self { + case .twentyFour: return 24 case .twentyEight: return 28 case .thirtySix: return 36 case .forty: return 40 @@ -660,6 +672,7 @@ extension ConversationAvatarView.Configuration.SizeClass { case .sixtyFour: return 64 case .eighty: return 80 case .eightyEight: return 88 + case .oneHundredTwelve: return 112 case .customDiameter(let diameter): return diameter } } @@ -669,13 +682,13 @@ extension ConversationAvatarView.Configuration.SizeClass { // But it turns out we need these badges displayed in more places than originally thought // Now we lerp between the explicit size classes that design has provided for us switch avatarDiameter { - case 0..<28: return CGFloat(avatarDiameter).inverseLerp(0, 28) * 16 - case 28...36: return 16 - case 36..<40: return CGFloat(avatarDiameter).inverseLerp(36, 40) * 24 + case ..<24: return 0 + case 24...36: return 16 + case 36..<40: return CGFloat(avatarDiameter).inverseLerp(36, 40).lerp(16, 24) case 40...64: return 24 - case 64..<80: return CGFloat(avatarDiameter).inverseLerp(64, 80) * 36 - case 80...88: return 36 - case 88...: return (CGFloat(avatarDiameter) / 88.0) * 36 + case 64..<80: return CGFloat(avatarDiameter).inverseLerp(64, 80).lerp(24, 36) + case 80...112: return 36 + case 112...: return (CGFloat(avatarDiameter) / 112) * 36 default: return 0 } } @@ -683,6 +696,7 @@ extension ConversationAvatarView.Configuration.SizeClass { /// The badge offset from its frame origin. Design has specified these points so the badge sits right alongside the circular avatar edge var badgeOffset: CGPoint { switch self { + case .twentyFour: return CGPoint(x: 10, y: 12) case .twentyEight: return CGPoint(x: 14, y: 16) case .thirtySix: return CGPoint(x: 20, y: 23) case .forty: return CGPoint(x: 20, y: 22) @@ -691,10 +705,10 @@ extension ConversationAvatarView.Configuration.SizeClass { case .sixtyFour: return CGPoint(x: 40, y: 46) case .eighty: return CGPoint(x: 44, y: 52) case .eightyEight: return CGPoint(x: 49, y: 56) + case .oneHundredTwelve: return CGPoint(x: 74, y: 80) case .customDiameter: // Design hand-selected the above offsets for each size class // For anything in between, let's just stick the badge at the bottom left of the frame for now - // TODO: We can probably do better here, check with design let avatarFrame = CGRect(origin: .zero, size: avatarSize) let offsetVector = CGVector(dx: -badgeSize.width, dy: -badgeSize.height) return avatarFrame.bottomRight.offsetBy(offsetVector) @@ -702,9 +716,11 @@ extension ConversationAvatarView.Configuration.SizeClass { } public func fetchImageFromBadgeAssets(_ badgeAssets: BadgeAssets) -> UIImage? { - // We're okay with downscaling badges for now. + // Never show a badge less than 16 points + // Otherwise, err on the side of downscaling over upscaling switch badgeDiameter { - case 0...16: return Theme.isDarkThemeEnabled ? badgeAssets.dark16 : badgeAssets.light16 + case ..<16: return nil + case 16: return Theme.isDarkThemeEnabled ? badgeAssets.dark16 : badgeAssets.light16 case 16...24: return Theme.isDarkThemeEnabled ? badgeAssets.dark24 : badgeAssets.light24 case 24...: return Theme.isDarkThemeEnabled ? badgeAssets.dark36 : badgeAssets.light36 default: return nil