Avatar updates when contact/profile/group photo changes

// FREEBIE
pull/1/head
Michael Kirk 7 years ago committed by Matthew Chen
parent b1bff71145
commit 7a1d24a9ab

@ -38,72 +38,79 @@ public class ConversationHeaderView: UIStackView {
}
}
public let titlePrimaryFont: UIFont = UIFont.ows_boldFont(withSize: 20)
public let titleSecondaryFont: UIFont = UIFont.ows_regularFont(withSize: 11)
public var avatarImage: UIImage? {
get {
return self.avatarView.image
}
set {
self.avatarView.image = newValue
}
}
public let titlePrimaryFont: UIFont = UIFont.ows_boldFont(withSize: 17)
public let titleSecondaryFont: UIFont = UIFont.ows_regularFont(withSize: 9)
public let subtitleFont: UIFont = UIFont.ows_regularFont(withSize: 12)
// public let columns: UIStackView
// public let textRows: UIStackView
private let titleLabel: UILabel
private let subtitleLabel: UILabel
private let avatarView: AvatarImageView
override init(frame: CGRect) {
public required init(thread: TSThread, contactsManager: OWSContactsManager) {
// TODO
// let avatarView: UIImageView = UIImageView()
let avatarView = ConversationAvatarImageView(thread: thread, diameter: 36, contactsManager: contactsManager)
self.avatarView = avatarView
// remove default border on avatarView
avatarView.layer.borderWidth = 0
titleLabel = UILabel()
titleLabel.textColor = .white
titleLabel.lineBreakMode = .byTruncatingTail
titleLabel.font = titlePrimaryFont
titleLabel.setContentHuggingHigh()
subtitleLabel = UILabel()
subtitleLabel.textColor = .white
subtitleLabel.lineBreakMode = .byTruncatingTail
subtitleLabel.font = subtitleFont
subtitleLabel.setContentHuggingHigh()
// textRows = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
// textRows.axis = .vertical
// textRows.alignment = .leading
let textRows = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
textRows.axis = .vertical
textRows.alignment = .leading
textRows.distribution = .fillProportionally
textRows.layoutMargins = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 4)
textRows.isLayoutMarginsRelativeArrangement = true
// columns = UIStackView(arrangedSubviews: [avatarView, textRows])
// low content hugging so that the text rows push container to the right bar button item(s)
textRows.setContentHuggingLow()
super.init(frame: frame)
super.init(frame: .zero)
self.layoutMargins = UIEdgeInsets(top: 4, left: 2, bottom: 4, right: 2)
self.isLayoutMarginsRelativeArrangement = true
// needed for proper layout on iOS10
self.translatesAutoresizingMaskIntoConstraints = false
self.axis = .vertical
self.distribution = .fillProportionally
self.alignment = .leading
self.axis = .horizontal
self.alignment = .center
self.spacing = 0
self.addArrangedSubview(titleLabel)
self.addArrangedSubview(subtitleLabel)
self.addArrangedSubview(avatarView)
self.addArrangedSubview(textRows)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapView))
self.addGestureRecognizer(tapGesture)
// titleLabel.setCompressionResistanceHigh()
// subtitleLabel.setCompressionResistanceHigh()
// self.setCompressionResistanceHigh()
// self.setContentHuggingLow()
// self.layoutIfNeeded()
// sizeToFit()
//
// self.translatesAutoresizingMaskIntoConstraints = true
// self.addSubview(columns)
// columns.autoPinEdgesToSuperviewEdges()
// self.addRedBorderRecursively()
}
required public init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
required public override init(frame: CGRect) {
fatalError("init(frame:) has not been implemented")
}
public override var intrinsicContentSize: CGSize {
// Grow to fill as much of the navbar as possible.
if #available(iOS 11, *) {

@ -489,6 +489,7 @@ typedef enum : NSUInteger {
[self createConversationScrollButtons];
[self createHeaderViews];
// [self createBackButton];
[self addNotificationListeners];
[self loadDraftInCompose];
@ -1114,22 +1115,26 @@ typedef enum : NSUInteger {
- (void)createHeaderViews
{
_backButtonUnreadCountView = [UIView new];
_backButtonUnreadCountView.layer.cornerRadius = self.unreadCountViewDiameter / 2;
_backButtonUnreadCountView.backgroundColor = [UIColor redColor];
_backButtonUnreadCountView.hidden = YES;
_backButtonUnreadCountView.userInteractionEnabled = NO;
_backButtonUnreadCountLabel = [UILabel new];
_backButtonUnreadCountLabel.backgroundColor = [UIColor clearColor];
_backButtonUnreadCountLabel.textColor = [UIColor whiteColor];
_backButtonUnreadCountLabel.font = [UIFont systemFontOfSize:11];
_backButtonUnreadCountLabel.textAlignment = NSTextAlignmentCenter;
// _backButtonUnreadCountView = [UIView new];
// _backButtonUnreadCountView.layer.cornerRadius = self.unreadCountViewDiameter / 2;
// _backButtonUnreadCountView.backgroundColor = [UIColor redColor];
// _backButtonUnreadCountView.hidden = YES;
// _backButtonUnreadCountView.userInteractionEnabled = NO;
//
// _backButtonUnreadCountLabel = [UILabel new];
// _backButtonUnreadCountLabel.backgroundColor = [UIColor clearColor];
// _backButtonUnreadCountLabel.textColor = [UIColor whiteColor];
// _backButtonUnreadCountLabel.font = [UIFont systemFontOfSize:11];
// _backButtonUnreadCountLabel.textAlignment = NSTextAlignmentCenter;
//
ConversationHeaderView *headerView = [ConversationHeaderView new];
ConversationHeaderView *headerView =
[[ConversationHeaderView alloc] initWithThread:self.thread contactsManager:self.contactsManager];
headerView.delegate = self;
// UIImage *avatarImage = [OWSAvatarBuilder buildImageForThread:self.thread diameter:36
// contactsManager:self.contactsManager]; headerView.avatarImage = avatarImage;
self.headerView = headerView;
#ifdef USE_DEBUG_UI
@ -3814,21 +3819,21 @@ typedef enum : NSUInteger {
- (void)setBackButtonUnreadCount:(NSUInteger)unreadCount
{
OWSAssertIsOnMainThread();
if (_backButtonUnreadCount == unreadCount) {
// No need to re-render same count.
return;
}
_backButtonUnreadCount = unreadCount;
OWSAssert(_backButtonUnreadCountView != nil);
_backButtonUnreadCountView.hidden = unreadCount <= 0;
OWSAssert(_backButtonUnreadCountLabel != nil);
// Max out the unread count at 99+.
const NSUInteger kMaxUnreadCount = 99;
_backButtonUnreadCountLabel.text = [OWSFormat formatInt:(int)MIN(kMaxUnreadCount, unreadCount)];
// OWSAssertIsOnMainThread();
// if (_backButtonUnreadCount == unreadCount) {
// // No need to re-render same count.
// return;
// }
// _backButtonUnreadCount = unreadCount;
//
// OWSAssert(_backButtonUnreadCountView != nil);
// _backButtonUnreadCountView.hidden = unreadCount <= 0;
//
// OWSAssert(_backButtonUnreadCountLabel != nil);
//
// // Max out the unread count at 99+.
// const NSUInteger kMaxUnreadCount = 99;
// _backButtonUnreadCountLabel.text = [OWSFormat formatInt:(int)MIN(kMaxUnreadCount, unreadCount)];
}
#pragma mark 3D Touch Preview Actions

@ -275,12 +275,12 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
}
// We don't show any text for the back button, so there's no need to localize it. But because we left align the
// conversation title view, we having a title which is just spaces, we can add tappable padding to the back button.
// conversation title view, we add a little tappable padding after the back button, by having a title of spaces.
// Admittedly this is kind of a hack and not super fine grained, but it's simple and results in the interactive pop
// gesture animating our title view properly vs. creating our own back button bar item and adjusting padding that
// way.
// gesture animating our title view nicely vs. creating our own back button bar item with custom padding, which does
// not properly animate with the "swipe to go back" or "swipe left for info" gestures.
self.navigationItem.backBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:@" " style:UIBarButtonItemStylePlain target:nil action:nil];
[[UIBarButtonItem alloc] initWithTitle:@" " style:UIBarButtonItemStylePlain target:nil action:nil];
if ([self.traitCollection respondsToSelector:@selector(forceTouchCapability)]
&& (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable)) {

@ -1,14 +1,127 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc
public class ConversationAvatarImageView: AvatarImageView {
let thread: TSThread
let diameter: UInt
let contactsManager: OWSContactsManager
// nil if group avatar
let recipientId: String?
// nil if contact avatar
let groupThreadId: String?
required public init(thread: TSThread, diameter: UInt, contactsManager: OWSContactsManager) {
self.thread = thread
self.diameter = diameter
self.contactsManager = contactsManager
switch thread {
case let contactThread as TSContactThread:
self.recipientId = contactThread.contactIdentifier()
self.groupThreadId = nil
case let groupThread as TSGroupThread:
self.recipientId = nil
self.groupThreadId = groupThread.uniqueId
default:
owsFail("in \(#function) unexpected thread type: \(thread)")
self.recipientId = nil
self.groupThreadId = nil
}
super.init(frame: .zero)
if recipientId != nil {
NotificationCenter.default.addObserver(self, selector: #selector(handleOtherUsersProfileChanged(notification:)), name: NSNotification.Name(rawValue: kNSNotificationName_OtherUsersProfileDidChange), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleSignalAccountsChanged(notification:)), name: NSNotification.Name.OWSContactsManagerSignalAccountsDidChange, object: nil)
}
if groupThreadId != nil {
NotificationCenter.default.addObserver(self, selector: #selector(handleGroupAvatarChanged(notification:)), name: .TSGroupThreadAvatarChanged, object: nil)
}
// TODO group avatar changed
self.updateImage()
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func handleSignalAccountsChanged(notification: Notification) {
Logger.debug("\(self.logTag) in \(#function)")
// It would be nice if we could do this only if *this* user changed,
// but currently this is a course grained notification.
self.updateImage()
}
func handleOtherUsersProfileChanged(notification: Notification) {
Logger.debug("\(self.logTag) in \(#function)")
guard let changedRecipientId = notification.userInfo?[kNSNotificationKey_ProfileRecipientId] as? String else {
owsFail("\(logTag) in \(#function) recipientId was unexpectedly nil")
return
}
guard let recipientId = self.recipientId else {
// shouldn't call this for group threads
owsFail("\(logTag) in \(#function) contactId was unexpectedly nil")
return
}
guard recipientId == recipientId else {
// not this avatar
return
}
self.updateImage()
}
func handleGroupAvatarChanged(notification: Notification) {
Logger.debug("\(self.logTag) in \(#function)")
guard let changedGroupThreadId = notification.userInfo?[TSGroupThread_NotificaitonKey_UniqueId] as? String else {
owsFail("\(logTag) in \(#function) groupThreadId was unexpectedly nil")
return
}
guard let groupThreadId = self.groupThreadId else {
// shouldn't call this for contact threads
owsFail("\(logTag) in \(#function) groupThreadId was unexpectedly nil")
return
}
guard groupThreadId == changedGroupThreadId else {
// not this avatar
return
}
thread.reload()
self.updateImage()
}
func updateImage() {
Logger.debug("\(self.logTag) in \(#function) updateImage")
self.image = OWSAvatarBuilder.buildImage(thread: thread, diameter: diameter, contactsManager: contactsManager)
}
}
@objc
public class AvatarImageView: UIImageView {
public init() {
super.init(frame: CGRect.zero)
super.init(frame: .zero)
self.configureView()
}
@ -28,6 +141,8 @@ public class AvatarImageView: UIImageView {
}
func configureView() {
self.autoPinToSquareAspectRatio()
self.layer.minificationFilter = kCAFilterTrilinear
self.layer.magnificationFilter = kCAFilterTrilinear
self.layer.borderWidth = 0.5

@ -21,6 +21,9 @@ NS_ASSUME_NONNULL_BEGIN
diameter:(NSUInteger)diameter
contactsManager:(OWSContactsManager *)contactsManager
{
OWSAssert(thread);
OWSAssert(contactsManager);
OWSAvatarBuilder *avatarBuilder;
if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;

@ -10,6 +10,9 @@ NS_ASSUME_NONNULL_BEGIN
@class TSAttachmentStream;
@class YapDatabaseReadWriteTransaction;
extern NSString *const TSGroupThreadAvatarChangedNotification;
extern NSString *const TSGroupThread_NotificaitonKey_UniqueId;
@interface TSGroupThread : TSThread
@property (nonatomic, strong) TSGroupModel *groupModel;

@ -12,6 +12,9 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const TSGroupThreadAvatarChangedNotification = @"TSGroupThreadAvatarChangedNotification";
NSString *const TSGroupThread_NotificaitonKey_UniqueId = @"TSGroupThread_NotificaitonKey_UniqueId";
@implementation TSGroupThread
#define TSGroupThreadPrefix @"g"
@ -192,6 +195,16 @@ NS_ASSUME_NONNULL_BEGIN
self.groupModel.groupImage = [attachmentStream image];
[self saveWithTransaction:transaction];
[transaction addCompletionQueue:nil
completionBlock:^{
NSDictionary *userInfo = @{ TSGroupThread_NotificaitonKey_UniqueId : self.uniqueId };
[[NSNotificationCenter defaultCenter]
postNotificationName:TSGroupThreadAvatarChangedNotification
object:self.uniqueId
userInfo:userInfo];
}];
// Avatars are stored directly in the database, so there's no need
// to keep the attachment around after assigning the image.
[attachmentStream removeWithTransaction:transaction];

@ -91,6 +91,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)save;
/**
* Assign the latest persisted values from the database.
*/
- (void)reload;
/**
* Saves the object with the shared readWrite connection - does not block.
*

@ -223,6 +223,17 @@ NS_ASSUME_NONNULL_BEGIN
}
}
- (void)reload
{
TSYapDatabaseObject *latest = [[self class] fetchObjectWithUniqueID:self.uniqueId];
if (!latest) {
OWSFail(@"%@ in %s `latest` was unexpectedly nil", self.logTag, __PRETTY_FUNCTION__);
return;
}
[self setValuesForKeysWithDictionary:latest.dictionaryValue];
}
@end
NS_ASSUME_NONNULL_END

Loading…
Cancel
Save