Simplify reconfigure API
This commit is contained in:
parent
6105ee5a20
commit
59645cd456
@ -305,7 +305,6 @@ private class GroupCallMemberCell: UITableViewCell {
|
||||
nameLabel.text = item.displayName
|
||||
avatarView.updateWithSneakyTransactionIfNecessary { config in
|
||||
config.dataSource = .address(item.address)
|
||||
return .asynchronously
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -252,7 +252,6 @@ class ProfileSettingsViewController: OWSTableViewController2 {
|
||||
} else {
|
||||
config.dataSource = nil
|
||||
}
|
||||
return .asynchronously
|
||||
}
|
||||
|
||||
cell.contentView.addSubview(badgedAvatarView)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -115,7 +115,6 @@ private class EmojiReactorCell: UITableViewCell {
|
||||
|
||||
avatarView.updateWithSneakyTransactionIfNecessary { config in
|
||||
config.dataSource = .address(item.address)
|
||||
return .asynchronously
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -443,7 +443,6 @@ struct ConversationHeaderBuilder: Dependencies {
|
||||
|
||||
avatarView.update(transaction) {
|
||||
$0.dataSource = .thread(delegate.thread)
|
||||
return .asynchronously
|
||||
}
|
||||
|
||||
// Track the most recent avatar view.
|
||||
|
||||
@ -63,7 +63,6 @@ import SignalUI
|
||||
|
||||
self.avatarView.updateWithSneakyTransactionIfNecessary { config in
|
||||
config.dataSource = .thread(thread)
|
||||
return .asynchronously
|
||||
}
|
||||
|
||||
if let accessoryMessage = accessoryMessage, !accessoryMessage.isEmpty {
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -352,7 +352,6 @@ private class MentionableUserCell: UITableViewCell {
|
||||
|
||||
avatarView.updateWithSneakyTransactionIfNecessary { configuration in
|
||||
configuration.dataSource = .address(mentionableUser.address)
|
||||
return .asynchronously
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user