Merge branch 'charlesmchen/cvc73'
This commit is contained in:
commit
fe95bcfb61
@ -100,7 +100,10 @@ public class CVComponentFooter: CVComponentBase, CVComponent {
|
||||
|
||||
let timestampLabel = componentView.timestampLabel
|
||||
let textColor: UIColor
|
||||
if isOverlayingMedia {
|
||||
if wasRemotelyDeleted {
|
||||
owsAssertDebug(!isOverlayingMedia)
|
||||
textColor = Theme.primaryTextColor
|
||||
} else if isOverlayingMedia {
|
||||
textColor = .ows_white
|
||||
} else if isOutsideBubble {
|
||||
textColor = Theme.secondaryTextAndIconColor
|
||||
@ -218,7 +221,7 @@ public class CVComponentFooter: CVComponentBase, CVComponent {
|
||||
}
|
||||
|
||||
private var tapForMoreLabelConfig: CVLabelConfig? {
|
||||
guard hasTapForMore else {
|
||||
guard hasTapForMore, !wasRemotelyDeleted else {
|
||||
return nil
|
||||
}
|
||||
guard let message = interaction as? TSMessage else {
|
||||
|
||||
@ -73,6 +73,7 @@ public class CVViewState: NSObject {
|
||||
public var scrollContinuity: ScrollContinuity = .bottom
|
||||
public var scrollContinuityMap: CVScrollContinuityMap?
|
||||
public var scrollActionForSizeTransition: CVScrollAction?
|
||||
public var scrollActionForUpdate: CVScrollAction?
|
||||
public var lastKnownDistanceFromBottom: CGFloat?
|
||||
@objc
|
||||
public var lastSearchedText: String?
|
||||
|
||||
@ -457,6 +457,7 @@ extension ConversationViewController: CVLoadCoordinatorDelegate {
|
||||
|
||||
let renderState = update.renderState
|
||||
let isScrolledToBottom = updateToken.isScrolledToBottom
|
||||
let viewState = self.viewState
|
||||
|
||||
var scrollAction = scrollActionParam
|
||||
|
||||
@ -469,23 +470,11 @@ extension ConversationViewController: CVLoadCoordinatorDelegate {
|
||||
owsFailDebug("Missing loadType.")
|
||||
}
|
||||
|
||||
let batchUpdatesBlock = {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let section = Self.messageSection
|
||||
var hasInserted = false
|
||||
var hasUpdated = false
|
||||
// Update scroll action to auto-scroll if necessary.
|
||||
if scrollAction.action == .none, !self.isUserScrolling {
|
||||
for item in items {
|
||||
switch item {
|
||||
case .insert(let renderItem, let newIndex):
|
||||
// Always perform inserts before updates.
|
||||
owsAssertDebug(!hasUpdated)
|
||||
|
||||
Logger.verbose("insert newIndex: \(newIndex)")
|
||||
|
||||
let indexPath = IndexPath(row: newIndex, section: section)
|
||||
self.collectionView.insertItems(at: [indexPath])
|
||||
hasInserted = true
|
||||
case .insert(let renderItem, _):
|
||||
|
||||
var wasJustInserted = false
|
||||
if let lastMessageForInboxSortId = updateToken.lastMessageForInboxSortId,
|
||||
@ -500,31 +489,54 @@ extension ConversationViewController: CVLoadCoordinatorDelegate {
|
||||
case .typingIndicator:
|
||||
isAutoScrollInteraction = true
|
||||
case .incomingMessage,
|
||||
.outgoingMessage,
|
||||
.call,
|
||||
.error,
|
||||
.info:
|
||||
.outgoingMessage,
|
||||
.call,
|
||||
.error,
|
||||
.info:
|
||||
isAutoScrollInteraction = wasJustInserted
|
||||
default:
|
||||
isAutoScrollInteraction = false
|
||||
}
|
||||
if scrollAction.action == .none,
|
||||
!self.isUserScrolling {
|
||||
if let outgoingMessage = renderItem.interaction as? TSOutgoingMessage,
|
||||
!outgoingMessage.isFromLinkedDevice,
|
||||
wasJustInserted {
|
||||
// Whenever we send an outgoing message from the local device,
|
||||
// auto-scroll to the bottom of the conversation, regardless
|
||||
// of scroll state.
|
||||
scrollAction = CVScrollAction(action: .bottomOfLoadWindow, isAnimated: false)
|
||||
} else if isAutoScrollInteraction,
|
||||
isScrolledToBottom {
|
||||
// If we're already at the bottom of the conversation and
|
||||
// a freshly inserted message or typing indicator appears,
|
||||
// auto-scroll to show it.
|
||||
scrollAction = CVScrollAction(action: .bottomOfLoadWindow, isAnimated: false)
|
||||
}
|
||||
|
||||
if let outgoingMessage = renderItem.interaction as? TSOutgoingMessage,
|
||||
!outgoingMessage.isFromLinkedDevice,
|
||||
wasJustInserted {
|
||||
// Whenever we send an outgoing message from the local device,
|
||||
// auto-scroll to the bottom of the conversation, regardless
|
||||
// of scroll state.
|
||||
scrollAction = CVScrollAction(action: .bottomOfLoadWindow, isAnimated: false)
|
||||
break
|
||||
} else if isAutoScrollInteraction,
|
||||
isScrolledToBottom {
|
||||
// If we're already at the bottom of the conversation and
|
||||
// a freshly inserted message or typing indicator appears,
|
||||
// auto-scroll to show it.
|
||||
scrollAction = CVScrollAction(action: .bottomOfLoadWindow, isAnimated: false)
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewState.scrollActionForUpdate = scrollAction
|
||||
|
||||
let batchUpdatesBlock = {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let section = Self.messageSection
|
||||
var hasInserted = false
|
||||
var hasUpdated = false
|
||||
for item in items {
|
||||
switch item {
|
||||
case .insert(_, let newIndex):
|
||||
// Always perform inserts before updates.
|
||||
owsAssertDebug(!hasUpdated)
|
||||
Logger.verbose("insert newIndex: \(newIndex)")
|
||||
let indexPath = IndexPath(row: newIndex, section: section)
|
||||
self.collectionView.insertItems(at: [indexPath])
|
||||
hasInserted = true
|
||||
case .update(_, let oldIndex, _):
|
||||
Logger.verbose("update oldIndex: \(oldIndex)")
|
||||
let indexPath = IndexPath(row: oldIndex, section: section)
|
||||
@ -554,6 +566,8 @@ extension ConversationViewController: CVLoadCoordinatorDelegate {
|
||||
|
||||
self.perform(scrollAction: scrollAction)
|
||||
|
||||
viewState.scrollActionForUpdate = nil
|
||||
|
||||
if !finished {
|
||||
Logger.warn("performBatchUpdates did not finish")
|
||||
Logger.warn("Layout: \(self.layout.debugDescription)")
|
||||
|
||||
@ -24,6 +24,7 @@ extension ConversationViewController {
|
||||
// with the updated -contentOffset in -scrollViewDidScroll:. So instead, we'll just see what layoutAttributes
|
||||
// are now in the collection view's visible content rect. This should be safe, since it's computed from the
|
||||
// already updated -contentOffset.
|
||||
layout.prepare()
|
||||
let visibleLayoutAttributes = layout.layoutAttributesForElements(in: visibleContentRect) ?? []
|
||||
|
||||
let lastVisibleIndexPath = visibleLayoutAttributes
|
||||
@ -91,6 +92,8 @@ extension ConversationViewController {
|
||||
}
|
||||
|
||||
private func percentOfIndexPathVisibleAboveBottom(_ indexPath: IndexPath) -> CGFloat {
|
||||
layout.prepare()
|
||||
|
||||
// If we don't have layout attributes, it's not visible
|
||||
guard let attributes = layout.layoutAttributesForItem(at: indexPath) else { return 0.0 }
|
||||
|
||||
|
||||
@ -87,7 +87,7 @@ public struct CVScrollAction: Equatable, CustomStringConvertible {
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
public var description: String {
|
||||
"action: \(action), isAnimated: \(isAnimated)"
|
||||
"[scrollAction: \(action), isAnimated: \(isAnimated)]"
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,6 +236,7 @@ extension ConversationViewController {
|
||||
guard !isUserScrolling else { return }
|
||||
|
||||
view.layoutIfNeeded()
|
||||
layout.prepare()
|
||||
|
||||
guard let attributes = layout.layoutAttributesForItem(at: indexPath) else {
|
||||
return owsFailDebug("failed to get attributes for indexPath \(indexPath)")
|
||||
@ -505,23 +506,28 @@ extension ConversationViewController {
|
||||
Logger.verbose("---- proposedContentOffset: \(proposedContentOffset)")
|
||||
|
||||
if isPresentingMessageActions,
|
||||
let contentOffset = contentOffsetForMessageActionInteraction {
|
||||
Logger.verbose("---- contentOffsetForMessageActionInteraction: \(contentOffset)")
|
||||
let contentOffset = targetContentOffsetForMessageActionInteraction {
|
||||
Logger.verbose("---- targetContentOffsetForMessageActionInteraction: \(contentOffset)")
|
||||
return contentOffset
|
||||
}
|
||||
|
||||
if let contentOffset = contentOffsetForSizeTransition() {
|
||||
Logger.verbose("---- contentOffsetForSizeTransition: \(contentOffset)")
|
||||
if let contentOffset = targetContentOffsetForSizeTransition() {
|
||||
Logger.verbose("---- targetContentOffsetForSizeTransition: \(contentOffset)")
|
||||
return contentOffset
|
||||
}
|
||||
|
||||
if let contentOffset = contentOffsetForScrollContinuityMap() {
|
||||
Logger.verbose("---- contentOffsetForScrollContinuityMap: \(contentOffset)")
|
||||
if let contentOffset = targetContentOffsetForUpdate() {
|
||||
Logger.verbose("---- targetContentOffsetForUpdate: \(contentOffset)")
|
||||
return contentOffset
|
||||
}
|
||||
|
||||
if let contentOffset = targetContentOffsetForScrollContinuityMap() {
|
||||
Logger.verbose("---- targetContentOffsetForScrollContinuityMap: \(contentOffset)")
|
||||
return contentOffset
|
||||
}
|
||||
|
||||
if scrollContinuity == .bottom,
|
||||
let contentOffset = contentOffsetForBottom() {
|
||||
let contentOffset = targetContentOffsetForBottom() {
|
||||
Logger.verbose("---- forLastKnownDistanceFromBottom: \(contentOffset)")
|
||||
return contentOffset
|
||||
}
|
||||
@ -530,7 +536,7 @@ extension ConversationViewController {
|
||||
return proposedContentOffset
|
||||
}
|
||||
|
||||
private func contentOffsetForBottom() -> CGPoint? {
|
||||
private func targetContentOffsetForBottom() -> CGPoint? {
|
||||
guard let lastKnownDistanceFromBottom = self.lastKnownDistanceFromBottom else {
|
||||
return nil
|
||||
}
|
||||
@ -607,7 +613,7 @@ extension ConversationViewController {
|
||||
|
||||
// We use this hook to ensure scroll state continuity. As the collection
|
||||
// view's content size changes, we want to keep the same cells in view.
|
||||
private func contentOffsetForScrollContinuityMap() -> CGPoint? {
|
||||
private func targetContentOffsetForScrollContinuityMap() -> CGPoint? {
|
||||
guard let scrollContinuityMap = viewState.scrollContinuityMap else {
|
||||
return nil
|
||||
}
|
||||
@ -652,10 +658,27 @@ extension ConversationViewController {
|
||||
return nil
|
||||
}
|
||||
|
||||
private func contentOffsetForSizeTransition() -> CGPoint? {
|
||||
private func targetContentOffsetForSizeTransition() -> CGPoint? {
|
||||
guard let scrollAction = viewState.scrollActionForSizeTransition else {
|
||||
return nil
|
||||
}
|
||||
owsAssertDebug(!scrollAction.isAnimated)
|
||||
return targetContentOffsetForScrollAction(scrollAction)
|
||||
}
|
||||
|
||||
private func targetContentOffsetForUpdate() -> CGPoint? {
|
||||
guard let scrollAction = viewState.scrollActionForUpdate else {
|
||||
return nil
|
||||
}
|
||||
guard scrollAction.action != .none, !scrollAction.isAnimated else {
|
||||
return nil
|
||||
}
|
||||
return targetContentOffsetForScrollAction(scrollAction)
|
||||
}
|
||||
|
||||
private func targetContentOffsetForScrollAction(_ scrollAction: CVScrollAction) -> CGPoint? {
|
||||
owsAssertDebug(!scrollAction.isAnimated)
|
||||
|
||||
layout.prepare()
|
||||
|
||||
switch scrollAction.action {
|
||||
@ -705,7 +728,7 @@ extension ConversationViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private var contentOffsetForMessageActionInteraction: CGPoint? {
|
||||
private var targetContentOffsetForMessageActionInteraction: CGPoint? {
|
||||
guard isPresentingMessageActions,
|
||||
let messageActionsViewController = messageActionsViewController else {
|
||||
owsFailDebug("Not presenting message actions.")
|
||||
@ -718,6 +741,7 @@ extension ConversationViewController {
|
||||
// This is expected if the menu action interaction is being deleted.
|
||||
return nil
|
||||
}
|
||||
layout.prepare()
|
||||
guard let layoutAttributes = layout.layoutAttributesForItem(at: indexPath) else {
|
||||
owsFailDebug("Missing layoutAttributes.")
|
||||
return nil
|
||||
|
||||
@ -200,6 +200,7 @@ extension ConversationViewController {
|
||||
return nil
|
||||
}
|
||||
|
||||
layout.prepare()
|
||||
guard let firstFrame = self.layout.layoutAttributesForItem(at: first)?.frame else {
|
||||
owsFailDebug("firstFrame was unexpectedly nil")
|
||||
return nil
|
||||
|
||||
@ -1830,7 +1830,7 @@ typedef enum : NSUInteger {
|
||||
return [self indexPathForInteractionUniqueId:messageActionInteractionId] == nil;
|
||||
}
|
||||
|
||||
- (nullable NSValue *)contentOffsetForMessageActionInteraction
|
||||
- (nullable NSValue *)targetContentOffsetForMessageActionInteraction
|
||||
{
|
||||
OWSAssertDebug(self.messageActionsViewController);
|
||||
|
||||
@ -1845,6 +1845,7 @@ typedef enum : NSUInteger {
|
||||
// This is expected if the menu action interaction is being deleted.
|
||||
return nil;
|
||||
}
|
||||
[self.layout prepareLayout];
|
||||
UICollectionViewLayoutAttributes *_Nullable layoutAttributes =
|
||||
[self.layout layoutAttributesForItemAtIndexPath:indexPath];
|
||||
if (layoutAttributes == nil) {
|
||||
|
||||
@ -208,8 +208,9 @@ public class ConversationViewLayout: UICollectionViewLayout {
|
||||
|
||||
@objc
|
||||
public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
var result = [UICollectionViewLayoutAttributes]()
|
||||
owsAssertDebug(hasLayout)
|
||||
|
||||
var result = [UICollectionViewLayoutAttributes]()
|
||||
if let headerLayoutAttributes = headerLayoutAttributes {
|
||||
result.append(headerLayoutAttributes)
|
||||
}
|
||||
@ -222,6 +223,8 @@ public class ConversationViewLayout: UICollectionViewLayout {
|
||||
|
||||
@objc
|
||||
public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
owsAssertDebug(hasLayout)
|
||||
|
||||
guard let attributes = itemAttributesMap[indexPath.row] else {
|
||||
Logger.verbose("Missing attributes: \(itemAttributesMap.keys)")
|
||||
return nil
|
||||
@ -231,6 +234,8 @@ public class ConversationViewLayout: UICollectionViewLayout {
|
||||
|
||||
@objc
|
||||
public override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
owsAssertDebug(hasLayout)
|
||||
|
||||
if elementKind == UICollectionView.elementKindSectionHeader {
|
||||
return headerLayoutAttributes
|
||||
} else if elementKind == UICollectionView.elementKindSectionFooter {
|
||||
|
||||
@ -1046,7 +1046,7 @@ extension ConversationSettingsViewController: ColorPickerDelegate {
|
||||
self.thread.updateConversationColorName(conversationColor.name, transaction: transaction)
|
||||
}
|
||||
|
||||
contactsManager.avatarCache.removeAllImages()
|
||||
contactsManager.removeAllFromAvatarCache()
|
||||
contactsManager.clearColorNameCache()
|
||||
updateTableContents()
|
||||
conversationSettingsViewDelegate?.conversationColorWasUpdated()
|
||||
|
||||
@ -26,8 +26,8 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification;
|
||||
|
||||
@property (nonatomic, readonly) SDSKeyValueStore *keyValueStore;
|
||||
|
||||
@property (nonnull, readonly) ImageCache *avatarCache;
|
||||
|
||||
// Do not access this property directly.
|
||||
@property (nonnull, readonly) ImageCache *avatarCachePrivate;
|
||||
|
||||
@property (atomic, readonly) NSArray<Contact *> *allContacts;
|
||||
|
||||
|
||||
@ -80,7 +80,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
_keyValueStore = [[SDSKeyValueStore alloc] initWithCollection:OWSContactsManagerCollection];
|
||||
|
||||
// TODO: We need to configure the limits of this cache.
|
||||
_avatarCache = [ImageCache new];
|
||||
_avatarCachePrivate = [ImageCache new];
|
||||
_colorNameCache = [NSCache new];
|
||||
|
||||
_allContacts = @[];
|
||||
@ -514,7 +514,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
SignalServiceAddress *address = notification.userInfo[kNSNotificationKey_ProfileAddress];
|
||||
OWSAssertDebug(address.isValid);
|
||||
|
||||
[self.avatarCache removeAllImagesForKey:address.stringForDisplay];
|
||||
[self removeAllFromAvatarCacheWithKey:address.stringForDisplay];
|
||||
}];
|
||||
}
|
||||
|
||||
@ -553,7 +553,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
[self.cnContactCache removeAllObjects];
|
||||
[self.cnContactAvatarCache removeAllObjects];
|
||||
|
||||
[self.avatarCache removeAllImages];
|
||||
[self removeAllFromAvatarCache];
|
||||
|
||||
[self intersectContacts:allContacts
|
||||
isUserRequested:isUserRequested
|
||||
|
||||
@ -13,6 +13,34 @@ public extension OWSContactsManager {
|
||||
return SDSDatabaseStorage.shared
|
||||
}
|
||||
|
||||
// MARK: - Avatar Cache
|
||||
|
||||
private static let unfairLock = UnfairLock()
|
||||
|
||||
func getImageFromAvatarCache(key: String, diameter: CGFloat) -> UIImage? {
|
||||
Self.unfairLock.withLock {
|
||||
self.avatarCachePrivate.image(forKey: key as NSString, diameter: diameter)
|
||||
}
|
||||
}
|
||||
|
||||
func setImageForAvatarCache(_ image: UIImage, forKey key: String, diameter: CGFloat) {
|
||||
Self.unfairLock.withLock {
|
||||
self.avatarCachePrivate.setImage(image, forKey: key as NSString, diameter: diameter)
|
||||
}
|
||||
}
|
||||
|
||||
func removeAllFromAvatarCacheWithKey(_ key: String) {
|
||||
Self.unfairLock.withLock {
|
||||
self.avatarCachePrivate.removeAllImages(forKey: key as NSString)
|
||||
}
|
||||
}
|
||||
|
||||
func removeAllFromAvatarCache() {
|
||||
Self.unfairLock.withLock {
|
||||
self.avatarCachePrivate.removeAllImages()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
func sortSignalAccountsWithSneakyTransaction(_ signalAccounts: [SignalAccount]) -> [SignalAccount] {
|
||||
|
||||
@ -297,7 +297,9 @@ public class DisplayableText: NSObject {
|
||||
|
||||
switch textValue {
|
||||
case .text(let text):
|
||||
let truncatedText = text.substring(to: snippetLength)
|
||||
let truncatedText = (text.substring(to: snippetLength)
|
||||
.ows_stripped()
|
||||
+ "…")
|
||||
truncatedContent = Content(textValue: .text(text: truncatedText),
|
||||
naturalAlignment: truncatedText.naturalTextAlignment)
|
||||
case .attributedText(let attributedText):
|
||||
|
||||
@ -155,8 +155,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
OWSCAssertDebug(self.address.isLocalAddress);
|
||||
NSString *noteToSelfCacheKey = [NSString stringWithFormat:@"%@:note-to-self", self.cacheKey];
|
||||
UIImage *_Nullable cachedAvatar =
|
||||
[OWSContactAvatarBuilder.contactsManager.avatarCache imageForKey:noteToSelfCacheKey
|
||||
diameter:(CGFloat)self.diameter];
|
||||
[OWSContactAvatarBuilder.contactsManager getImageFromAvatarCacheWithKey:noteToSelfCacheKey
|
||||
diameter:(CGFloat)self.diameter];
|
||||
if (cachedAvatar) {
|
||||
return cachedAvatar;
|
||||
}
|
||||
@ -167,9 +167,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return nil;
|
||||
}
|
||||
|
||||
[OWSContactAvatarBuilder.contactsManager.avatarCache setImage:image
|
||||
forKey:noteToSelfCacheKey
|
||||
diameter:self.diameter];
|
||||
[OWSContactAvatarBuilder.contactsManager setImageForAvatarCache:image
|
||||
forKey:noteToSelfCacheKey
|
||||
diameter:self.diameter];
|
||||
return image;
|
||||
}
|
||||
|
||||
@ -205,7 +205,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (nullable UIImage *)buildDefaultImage
|
||||
{
|
||||
UIImage *_Nullable cachedAvatar =
|
||||
[OWSContactAvatarBuilder.contactsManager.avatarCache imageForKey:self.cacheKey diameter:(CGFloat)self.diameter];
|
||||
[OWSContactAvatarBuilder.contactsManager getImageFromAvatarCacheWithKey:self.cacheKey
|
||||
diameter:(CGFloat)self.diameter];
|
||||
if (cachedAvatar) {
|
||||
return cachedAvatar;
|
||||
}
|
||||
@ -245,7 +246,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return nil;
|
||||
}
|
||||
|
||||
[OWSContactAvatarBuilder.contactsManager.avatarCache setImage:image forKey:self.cacheKey diameter:self.diameter];
|
||||
[OWSContactAvatarBuilder.contactsManager setImageForAvatarCache:image forKey:self.cacheKey diameter:self.diameter];
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
stringWithFormat:@"%@-%d-%lu", groupId.hexadecimalString, Theme.isDarkThemeEnabled, (unsigned long)diameter];
|
||||
|
||||
UIImage *_Nullable cachedAvatar =
|
||||
[OWSGroupAvatarBuilder.contactsManager.avatarCache imageForKey:cacheKey diameter:(CGFloat)diameter];
|
||||
[OWSGroupAvatarBuilder.contactsManager getImageFromAvatarCacheWithKey:cacheKey diameter:(CGFloat)diameter];
|
||||
if (cachedAvatar) {
|
||||
return cachedAvatar;
|
||||
}
|
||||
@ -85,7 +85,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return nil;
|
||||
}
|
||||
|
||||
[OWSGroupAvatarBuilder.contactsManager.avatarCache setImage:image forKey:cacheKey diameter:diameter];
|
||||
[OWSGroupAvatarBuilder.contactsManager setImageForAvatarCache:image forKey:cacheKey diameter:diameter];
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user