Simplify reconfigure API

This commit is contained in:
Michelle Linington 2021-11-01 23:29:25 -07:00
parent 6105ee5a20
commit 59645cd456
22 changed files with 81 additions and 78 deletions

View File

@ -305,7 +305,6 @@ private class GroupCallMemberCell: UITableViewCell {
nameLabel.text = item.displayName
avatarView.updateWithSneakyTransactionIfNecessary { config in
config.dataSource = .address(item.address)
return .asynchronously
}
}
}

View File

@ -254,7 +254,7 @@ class GroupCallRemoteMemberView: GroupCallMemberView {
var deferredReconfigTimer: Timer?
let errorView = GroupCallErrorView()
let spinner = UIActivityIndicatorView(style: .whiteLarge)
let avatarView = ConversationAvatarView(sizeClass: .customDiameter(0))
let avatarView = ConversationAvatarView()
var isCallMinimized: Bool = false {
didSet {
@ -329,7 +329,6 @@ class GroupCallRemoteMemberView: GroupCallMemberView {
avatarView.update(transaction) { config in
config.dataSource = .address(device.address)
config.sizeClass = .customDiameter(updatedSize)
return .asynchronously
}
return self.contactsManagerImpl.avatarImage(forAddress: device.address,
@ -412,7 +411,6 @@ class GroupCallRemoteMemberView: GroupCallMemberView {
avatarView.updateWithSneakyTransactionIfNecessary { config in
config.sizeClass = .customDiameter(avatarDiameter)
return .asynchronously
}
}

View File

@ -500,7 +500,6 @@ class IndividualCallViewController: OWSViewController, CallObserver, CallAudioSe
databaseStorage.read { transaction in
contactAvatarView.update(transaction) { config in
config.dataSource = .thread(thread)
return .asynchronously
}
backgroundAvatarView.image = contactsManagerImpl.avatarImage(forAddress: thread.contactAddress,
shouldValidate: true,

View File

@ -74,7 +74,6 @@ public class PaymentModelCell: UITableViewCell {
if let address = paymentItem.address {
contactAvatarView.updateWithSneakyTransactionIfNecessary { config in
config.dataSource = .address(address)
return .asynchronously
}
avatarView = contactAvatarView
} else {

View File

@ -368,7 +368,6 @@ class PaymentsDetailViewController: OWSTableViewController2 {
databaseStorage.read { transaction in
avatarView.update(transaction) { config in
config.dataSource = .address(address)
return .asynchronously
}
let username = Self.contactsManager.displayName(for: address, transaction: transaction)

View File

@ -252,7 +252,6 @@ class ProfileSettingsViewController: OWSTableViewController2 {
} else {
config.dataSource = nil
}
return .asynchronously
}
cell.contentView.addSubview(badgedAvatarView)

View File

@ -375,7 +375,7 @@ public class CVComponentMessage: CVComponentBase, CVRootComponent {
// No transaction will be necessary since our data source is preloaded
componentView.avatarView.updateWithSneakyTransactionIfNecessary { config in
config.dataSource = senderAvatar.avatarDataSource
return .synchronously
config.applyConfigurationSynchronously()
}
}
// Add the view wrapper, not the view.
@ -2173,7 +2173,6 @@ public class CVComponentMessage: CVComponentBase, CVRootComponent {
let avatarView = ConversationAvatarView(sizeClass: componentView.avatarView.configuration.sizeClass)
avatarView.updateWithSneakyTransactionIfNecessary { newConfig in
newConfig = componentView.avatarView.configuration
return .synchronously
}
avatarView.frame = componentView.avatarView.bounds
let isRTL = CurrentAppContext().isRTL

View File

@ -79,7 +79,7 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent {
avatarView.updateWithSneakyTransactionIfNecessary { configuration in
// Transaction isn't necessary since we pre-fetched our data source
configuration.dataSource = avatarDataSource
return .synchronously
configuration.applyConfigurationSynchronously()
}
componentView.avatarView = avatarView
if threadDetails.isAvatarBlurred {

View File

@ -64,7 +64,7 @@ public class CVComponentTypingIndicator: CVComponentBase, CVRootComponent {
// No transaction is necessary since our data source is already loaded
avatarView.updateWithSneakyTransactionIfNecessary { config in
config.dataSource = avatarDataSource
return .synchronously
config.applyConfigurationSynchronously()
}
outerViews.append(avatarView)
}

View File

@ -121,7 +121,7 @@ public class ConversationHeaderView: UIStackView {
public func configure(thread: TSThread) {
avatarView.updateWithSneakyTransactionIfNecessary { config in
config.dataSource = .thread(thread)
return .synchronously
config.applyConfigurationSynchronously()
}
}
@ -137,7 +137,7 @@ public class ConversationHeaderView: UIStackView {
}
public func updateAvatar() {
avatarView.updateWithSneakyTransactionIfNecessary { _ in .asynchronously }
avatarView.updateWithSneakyTransactionIfNecessary { _ in }
}
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
@ -145,7 +145,6 @@ public class ConversationHeaderView: UIStackView {
avatarView.updateWithSneakyTransactionIfNecessary { config in
config.sizeClass = avatarSizeClass
config.addBadgeIfApplicable = shouldBadgeAvatar
return .asynchronously
}
}

View File

@ -115,7 +115,6 @@ private class EmojiReactorCell: UITableViewCell {
avatarView.updateWithSneakyTransactionIfNecessary { config in
config.dataSource = .address(item.address)
return .asynchronously
}
}
}

View File

@ -165,7 +165,6 @@ public class GroupMigrationActionSheet: UIView {
let avatarView = ConversationAvatarView(sizeClass: .customDiameter(28))
avatarView.update(transaction) { config in
config.dataSource = .address(address)
return .asynchronously
}
let label = buildLabel()

View File

@ -313,7 +313,9 @@ public class HomeViewCell: UITableViewCell {
avatarView.updateWithSneakyTransactionIfNecessary { config in
config.dataSource = .thread(cellContentToken.thread)
return cellContentToken.shouldLoadAvatarAsync ? .asynchronously : .synchronously
if !cellContentToken.shouldLoadAvatarAsync {
config.applyConfigurationSynchronously()
}
}
typingIndicatorView.configureForHomeView()

View File

@ -80,7 +80,6 @@ public extension HomeViewController {
let avatarView = ConversationAvatarView(sizeClass: .customDiameter(Self.paymentsBannerAvatarSize))
avatarView.update(transaction) { config in
config.dataSource = .address(address)
return .asynchronously
}
let paymentsHistoryItem = PaymentsHistoryItem(paymentModel: paymentModel,
@ -240,8 +239,8 @@ public extension HomeViewController {
avatarView.update(readTx) { config in
if let address = tsAccountManager.localAddress(with: readTx) {
config.dataSource = .address(address)
config.applyConfigurationSynchronously()
}
return .synchronously
}
}
return avatarView

View File

@ -228,7 +228,6 @@ private class NewGroupMemberCell: UICollectionViewCell {
self.member = member
avatarView.updateWithSneakyTransactionIfNecessary { config in
config.dataSource = .address(member.address)
return .asynchronously
}
textLabel.text = member.shortName
}

View File

@ -58,7 +58,6 @@ public class ReturnToCallViewController: UIViewController {
let profileImage = databaseStorage.read { transaction -> UIImage? in
avatarView.update(transaction) { config in
config.dataSource = .address(callViewController.remoteVideoAddress)
return .asynchronously
}
return self.profileManagerImpl.profileAvatar(for: callViewController.remoteVideoAddress,
transaction: transaction)

View File

@ -443,7 +443,6 @@ struct ConversationHeaderBuilder: Dependencies {
avatarView.update(transaction) {
$0.dataSource = .thread(delegate.thread)
return .asynchronously
}
// Track the most recent avatar view.

View File

@ -63,7 +63,6 @@ import SignalUI
self.avatarView.updateWithSneakyTransactionIfNecessary { config in
config.dataSource = .thread(thread)
return .asynchronously
}
if let accessoryMessage = accessoryMessage, !accessoryMessage.isEmpty {

View File

@ -226,7 +226,6 @@ class NameCollisionCell: UITableViewCell {
avatarView.updateWithSneakyTransactionIfNecessary { config in
config.dataSource = .address(model.address)
return .asynchronously
}
if model.address.isLocalAddress {
nameLabel.text = NSLocalizedString(

View File

@ -163,7 +163,6 @@ public class ContactCellView: ManualStackView {
config.dataSource = avatarDataSource
config.addBadgeIfApplicable = configuration.badged
config.localUserDisplayMode = configuration.localUserDisplayMode
return .asynchronously
}
// Update fonts to reflect changes to dynamic type.

View File

@ -8,7 +8,7 @@ import SignalMessaging
public class ConversationAvatarView: UIView, CVView, PrimaryImageView {
public required init(
sizeClass: Configuration.SizeClass,
sizeClass: Configuration.SizeClass = .customDiameter(0),
localUserDisplayMode: LocalUserDisplayMode = .asUser,
badged: Bool = false,
shape: Configuration.Shape = .circular,
@ -78,13 +78,33 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView {
case circular
}
/// The preferred size class of the avatar. Used for avatar generation and autolayout (if enabled)
/// If a predefined size class is used, a badge can optionally be placed by specifying `addBadgeIfApplicable`
public var sizeClass: SizeClass
/// The data provider used to fetch an avatar and badge
public var dataSource: ConversationAvatarDataSource?
/// Adjusts how the local user profile avatar is generated (Note to Self or Avatar?)
public var localUserDisplayMode: LocalUserDisplayMode
/// Places the user's badge (if they have one) over the avatar. Only supported for predefined size classes
public var addBadgeIfApplicable: Bool
/// Adjusts the mask of the avatar view
public var shape: Shape
/// If set `true`, adds constraints to the view to ensure that it's sized for the provided size class
/// Otherwise, it's the superview's responsibility to ensure this view is sized appropriately
public var useAutolayout: Bool
// Adopters that'd like to fetch the image synchronously can set this to perform
// the next model update synchronously if necessary.
fileprivate var updateSynchronously: Bool = false
public mutating func applyConfigurationSynchronously() {
updateSynchronously = true
}
fileprivate mutating func checkForSyncUpdateAndClear() -> Bool {
let shouldUpdateSync = updateSynchronously
updateSynchronously = false
return shouldUpdateSync
}
}
public private(set) var configuration: Configuration {
@ -94,67 +114,74 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView {
owsFailDebug("Invalid configuration. Badging not supported with custom size classes")
configuration.addBadgeIfApplicable = false
}
// We may need to update our model, layout, or constraints based on the changes to the configuration
let sizeClassDidChange = configuration.sizeClass != oldValue.sizeClass
let dataSourceDidChange = configuration.dataSource != oldValue.dataSource
let localUserDisplayModeDidChange = configuration.localUserDisplayMode != oldValue.localUserDisplayMode
let shouldShowBadgeDidChange = configuration.addBadgeIfApplicable != oldValue.addBadgeIfApplicable
let shapeDidChange = configuration.shape != oldValue.shape
let autolayoutDidChange = configuration.useAutolayout != oldValue.useAutolayout
// Any changes to avatar size or provider will trigger a model update
if sizeClassDidChange || dataSourceDidChange || localUserDisplayModeDidChange || shouldShowBadgeDidChange {
setNeedsModelUpdate()
}
// If autolayout was toggled, or the size changed while autolayout is enabled we need to update our constraints
if autolayoutDidChange || (configuration.useAutolayout && sizeClassDidChange) {
setNeedsUpdateConstraints()
}
if sizeClassDidChange || shouldShowBadgeDidChange || shapeDidChange {
setNeedsLayout()
if configuration.dataSource != oldValue.dataSource {
ensureObservers()
}
}
}
public enum UpdateBehavior {
case synchronously
case asynchronously
func updateConfigurationAndSetDirtyIfNecessary(_ newValue: Configuration) {
let oldValue = configuration
configuration = newValue
// We may need to update our model, layout, or constraints based on the changes to the configuration
let sizeClassDidChange = configuration.sizeClass != oldValue.sizeClass
let dataSourceDidChange = configuration.dataSource != oldValue.dataSource
let localUserDisplayModeDidChange = configuration.localUserDisplayMode != oldValue.localUserDisplayMode
let shouldShowBadgeDidChange = configuration.addBadgeIfApplicable != oldValue.addBadgeIfApplicable
let shapeDidChange = configuration.shape != oldValue.shape
let autolayoutDidChange = configuration.useAutolayout != oldValue.useAutolayout
// Any changes to avatar size or provider will trigger a model update
if sizeClassDidChange || dataSourceDidChange || localUserDisplayModeDidChange || shouldShowBadgeDidChange {
setNeedsModelUpdate()
}
// If autolayout was toggled, or the size changed while autolayout is enabled we need to update our constraints
if autolayoutDidChange || (configuration.useAutolayout && sizeClassDidChange) {
setNeedsUpdateConstraints()
}
if sizeClassDidChange || shouldShowBadgeDidChange || shapeDidChange {
setNeedsLayout()
}
}
public func updateWithSneakyTransactionIfNecessary(_ block: (inout Configuration) -> UpdateBehavior) {
update(optionalTransaction: nil, block)
// MARK: Configuration updates
public func updateWithSneakyTransactionIfNecessary(_ updateBlock: (inout Configuration) -> Void) {
update(optionalTransaction: nil, updateBlock)
}
/// To reduce the occurrence of unnecessary avatar fetches, updates to the view configuration occur in a closure
/// Callers can specify a synchronous or asynchronous model update through the closure's return value
public func update(_ transaction: SDSAnyReadTransaction, _ block: (inout Configuration) -> UpdateBehavior) {
/// Configuration updates will be applied all at once
public func update(_ transaction: SDSAnyReadTransaction, _ updateBlock: (inout Configuration) -> Void) {
AssertIsOnMainThread()
update(optionalTransaction: transaction, block)
update(optionalTransaction: transaction, updateBlock)
}
private func update(optionalTransaction transaction: SDSAnyReadTransaction?, _ block: (inout Configuration) -> UpdateBehavior) {
private func update(optionalTransaction transaction: SDSAnyReadTransaction?, _ updateBlock: (inout Configuration) -> Void) {
AssertIsOnMainThread()
let oldConfiguration = configuration
var mutableConfig = oldConfiguration
let updateBehavior = block(&mutableConfig)
configuration = mutableConfig
updateModelIfNecessary(updateBehavior, transaction: transaction)
updateBlock(&mutableConfig)
updateConfigurationAndSetDirtyIfNecessary(mutableConfig)
updateModelIfNecessary(transaction: transaction)
}
// MARK: Model Updates
// Forces a model update
private func updateModel(_ behavior: UpdateBehavior, transaction readTx: SDSAnyReadTransaction) {
private func updateModel(transaction readTx: SDSAnyReadTransaction?) {
setNeedsModelUpdate()
updateModelIfNecessary(behavior, transaction: readTx)
updateModelIfNecessary(transaction: readTx)
}
// If the model has been dirtied, performs an update
// If an async update is requested, the model is updated immediately with any available chached content
// followed by enqueueing a full model update on a background thread.
private func updateModelIfNecessary(_ behavior: UpdateBehavior, transaction readTx: SDSAnyReadTransaction?) {
private func updateModelIfNecessary(transaction readTx: SDSAnyReadTransaction?) {
AssertIsOnMainThread()
guard nextModelGeneration.get() > currentModelGeneration else { return }
@ -163,17 +190,15 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView {
return
}
switch behavior {
case .synchronously:
let updateSynchronously = configuration.checkForSyncUpdateAndClear()
if updateSynchronously {
let avatarImage = dataSource.buildImage(configuration: configuration, transaction: readTx)
let badgeImage = dataSource.fetchBadge(configuration: configuration, transaction: readTx)
updateViewContent(avatarImage: avatarImage, badgeImage: badgeImage)
case .asynchronously:
} else {
let avatarImage = dataSource.fetchCachedImage(configuration: configuration, transaction: readTx)
let badgeImage = dataSource.fetchBadge(configuration: configuration, transaction: readTx)
updateViewContent(avatarImage: avatarImage, badgeImage: badgeImage)
enqueueAsyncModelUpdate()
}
}
@ -346,10 +371,7 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView {
@objc
private func themeDidChange() {
AssertIsOnMainThread()
databaseStorage.read { readTx in
updateModel(.asynchronously, transaction: readTx)
}
updateModel(transaction: nil)
}
@objc
@ -358,9 +380,7 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView {
// PERF: It would be nice if we could do this only if *this* user's SignalAccount changed,
// but currently this is only a course grained notification.
databaseStorage.read { readTx in
updateModel(.asynchronously, transaction: readTx)
}
updateModel(transaction: nil)
}
@objc
@ -393,7 +413,7 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView {
}
if providerAddress == address {
databaseStorage.read { updateModel(.asynchronously, transaction: $0) }
updateModel(transaction: nil)
}
}
@ -429,7 +449,7 @@ public class ConversationAvatarView: UIView, CVView, PrimaryImageView {
if contentThreadId == changedThreadId {
databaseStorage.read {
dataSource.reload(transaction: $0)
updateModel(.asynchronously, transaction: $0)
updateModel(transaction: $0)
}
}
}

View File

@ -352,7 +352,6 @@ private class MentionableUserCell: UITableViewCell {
avatarView.updateWithSneakyTransactionIfNecessary { configuration in
configuration.dataSource = .address(mentionableUser.address)
return .asynchronously
}
}
}