Merge branch 'mkirk/profile-pic-in-conversation'

pull/1/head
Matthew Chen 8 years ago
commit 945ab7e4a2

@ -195,7 +195,6 @@
34CF078A203E6B78005C4D61 /* end_call_tone_cept.caf in Resources */ = {isa = PBXBuildFile; fileRef = 34CF0786203E6B78005C4D61 /* end_call_tone_cept.caf */; };
34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F04F1F7D45A60066283D /* GifPickerCell.swift */; };
34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */; };
34D1F0821F8678AA0066283D /* ConversationHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0661F8678AA0066283D /* ConversationHeaderView.m */; };
34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0681F8678AA0066283D /* ConversationInputTextView.m */; };
34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F06A1F8678AA0066283D /* ConversationInputToolbar.m */; };
34D1F0861F8678AA0066283D /* ConversationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F06E1F8678AA0066283D /* ConversationViewController.m */; };
@ -384,6 +383,7 @@
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D231761DC7E8F10034FA89 /* SessionResetJob.swift */; };
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */; };
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */ = {isa = PBXBuildFile; fileRef = 45D308AC2049A439000189E4 /* PinEntryView.m */; };
45DDA6242090CEB500DE97F8 /* ConversationHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */; };
45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */; };
45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; };
45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */; };
@ -811,8 +811,6 @@
34CF0786203E6B78005C4D61 /* end_call_tone_cept.caf */ = {isa = PBXFileReference; lastKnownFileType = file; name = end_call_tone_cept.caf; path = Signal/AudioFiles/end_call_tone_cept.caf; sourceTree = SOURCE_ROOT; };
34D1F04F1F7D45A60066283D /* GifPickerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerCell.swift; sourceTree = "<group>"; };
34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphyDownloader.swift; sourceTree = "<group>"; };
34D1F0651F8678AA0066283D /* ConversationHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConversationHeaderView.h; sourceTree = "<group>"; };
34D1F0661F8678AA0066283D /* ConversationHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversationHeaderView.m; sourceTree = "<group>"; };
34D1F0671F8678AA0066283D /* ConversationInputTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConversationInputTextView.h; sourceTree = "<group>"; };
34D1F0681F8678AA0066283D /* ConversationInputTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversationInputTextView.m; sourceTree = "<group>"; };
34D1F0691F8678AA0066283D /* ConversationInputToolbar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConversationInputToolbar.h; sourceTree = "<group>"; };
@ -1023,6 +1021,7 @@
45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWS2FAReminderViewController.swift; sourceTree = "<group>"; };
45D308AB2049A439000189E4 /* PinEntryView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PinEntryView.h; sourceTree = "<group>"; };
45D308AC2049A439000189E4 /* PinEntryView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PinEntryView.m; sourceTree = "<group>"; };
45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationHeaderView.swift; sourceTree = "<group>"; };
45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompareSafetyNumbersActivity.swift; sourceTree = "<group>"; };
45E282DE1D08E67800ADD4C8 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = translations/gl.lproj/Localizable.strings; sourceTree = "<group>"; };
45E282DF1D08E6CC00ADD4C8 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = translations/id.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -1472,8 +1471,6 @@
34D1F0951F867BFC0066283D /* Cells */,
34D1F0B21F86D31D0066283D /* ConversationCollectionView.h */,
34D1F0B31F86D31D0066283D /* ConversationCollectionView.m */,
34D1F0651F8678AA0066283D /* ConversationHeaderView.h */,
34D1F0661F8678AA0066283D /* ConversationHeaderView.m */,
34D1F0671F8678AA0066283D /* ConversationInputTextView.h */,
34D1F0681F8678AA0066283D /* ConversationInputTextView.m */,
34D1F0691F8678AA0066283D /* ConversationInputToolbar.h */,
@ -1486,6 +1483,7 @@
34D1F0701F8678AA0066283D /* ConversationViewItem.m */,
34D1F0711F8678AA0066283D /* ConversationViewLayout.h */,
34D1F0721F8678AA0066283D /* ConversationViewLayout.m */,
45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */,
);
path = ConversationView;
sourceTree = "<group>";
@ -3140,7 +3138,6 @@
B6DA6B071B8A2F9A00CA6F98 /* AppStoreRating.m in Sources */,
451A13B11E13DED2000A50FD /* CallNotificationsAdapter.swift in Sources */,
450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */,
34D1F0821F8678AA0066283D /* ConversationHeaderView.m in Sources */,
34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */,
340FC8C7204DE64D007AEB0F /* OWSBackupAPI.swift in Sources */,
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */,
@ -3248,6 +3245,7 @@
45BB93381E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */,
34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */,
458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */,
45DDA6242090CEB500DE97F8 /* ConversationHeaderView.swift in Sources */,
45F32C242057297A00A300D5 /* MessageDetailViewController.swift in Sources */,
34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */,
457F671B20746193000EABCD /* QuotedReplyPreview.swift in Sources */,

@ -7,7 +7,6 @@
// Separate iOS Frameworks from other imports.
#import "AppSettingsViewController.h"
#import "ConversationHeaderView.h"
#import "ConversationViewItem.h"
#import "DateUtil.h"
#import "DebugUIPage.h"

@ -1,14 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface ConversationHeaderView : UIView
@property (nonatomic) UILabel *titleLabel;
@property (nonatomic) UILabel *subtitleLabel;
@end
NS_ASSUME_NONNULL_END

@ -1,65 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "ConversationHeaderView.h"
#import "UIView+OWS.h"
NS_ASSUME_NONNULL_BEGIN
@implementation ConversationHeaderView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.layoutMargins = UIEdgeInsetsZero;
}
return self;
}
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
[self layoutSubviews];
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
[self layoutSubviews];
}
- (void)setCenter:(CGPoint)center
{
[super setCenter:center];
[self layoutSubviews];
}
- (void)layoutSubviews
{
[super layoutSubviews];
// We need to manually resize and position the title views;
// iOS AutoLayout doesn't work inside navigation bar items.
const int kTitleVSpacing = 0.f;
const int kTitleHMargin = 0.f;
CGFloat titleHeight = ceil([self.titleLabel sizeThatFits:CGSizeZero].height);
CGFloat subtitleHeight = ceil([self.subtitleLabel sizeThatFits:CGSizeZero].height);
CGFloat contentHeight = titleHeight + kTitleVSpacing + subtitleHeight;
CGFloat contentWidth = round(self.width - 2 * kTitleHMargin);
CGFloat y = MAX(0, round((self.height - contentHeight) * 0.5f));
self.titleLabel.frame = CGRectMake(kTitleHMargin, y, contentWidth, titleHeight);
self.subtitleLabel.frame
= CGRectMake(kTitleHMargin, ceil(y + titleHeight + kTitleVSpacing), contentWidth, subtitleHeight);
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,120 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
public protocol ConversationHeaderViewDelegate {
func didTapConversationHeaderView(_ conversationHeaderView: ConversationHeaderView)
}
@objc
public class ConversationHeaderView: UIStackView {
public weak var delegate: ConversationHeaderViewDelegate?
public var attributedTitle: NSAttributedString? {
get {
return self.titleLabel.attributedText
}
set {
self.titleLabel.attributedText = newValue
}
}
public var attributedSubtitle: NSAttributedString? {
get {
return self.subtitleLabel.attributedText
}
set {
self.subtitleLabel.attributedText = newValue
}
}
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)
private let titleLabel: UILabel
private let subtitleLabel: UILabel
private let avatarView: AvatarImageView
public required init(thread: TSThread, contactsManager: OWSContactsManager) {
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()
let textRows = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
textRows.axis = .vertical
textRows.alignment = .leading
textRows.distribution = .fillProportionally
textRows.spacing = 0
textRows.layoutMargins = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
textRows.isLayoutMarginsRelativeArrangement = true
// low content hugging so that the text rows push container to the right bar button item(s)
textRows.setContentHuggingLow()
super.init(frame: .zero)
self.layoutMargins = UIEdgeInsets(top: 4, left: 2, bottom: 4, right: 2)
self.isLayoutMarginsRelativeArrangement = true
self.axis = .horizontal
self.alignment = .center
self.spacing = 0
self.addArrangedSubview(avatarView)
self.addArrangedSubview(textRows)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapView))
self.addGestureRecognizer(tapGesture)
}
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.
return UILayoutFittingExpandedSize
}
// MARK: Delegate Methods
func didTapView(tapGesture: UITapGestureRecognizer) {
guard tapGesture.state == .recognized else {
return
}
self.delegate?.didTapConversationHeaderView(self)
}
}

@ -8,7 +8,6 @@
#import "BlockListViewController.h"
#import "ContactsViewHelper.h"
#import "ConversationCollectionView.h"
#import "ConversationHeaderView.h"
#import "ConversationInputTextView.h"
#import "ConversationInputToolbar.h"
#import "ConversationScrollButton.h"
@ -124,6 +123,7 @@ typedef enum : NSUInteger {
CNContactViewControllerDelegate,
DisappearingTimerConfigurationViewDelegate,
OWSConversationSettingsViewDelegate,
ConversationHeaderViewDelegate,
ConversationViewLayoutDelegate,
ConversationViewCellDelegate,
ConversationInputTextViewDelegate,
@ -178,9 +178,7 @@ typedef enum : NSUInteger {
@property (nonatomic, nullable) NSTimer *readTimer;
@property (nonatomic) NSCache *cellMediaCache;
@property (nonatomic) ConversationHeaderView *navigationBarTitleView;
@property (nonatomic) UILabel *navigationBarTitleLabel;
@property (nonatomic) UILabel *navigationBarSubtitleLabel;
@property (nonatomic) ConversationHeaderView *headerView;
@property (nonatomic, nullable) UIView *bannerView;
@property (nonatomic, nullable) OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration;
@ -350,7 +348,7 @@ typedef enum : NSUInteger {
if (recipientId.length > 0 && [self.thread.recipientIdentifiers containsObject:recipientId]) {
if ([self.thread isKindOfClass:[TSContactThread class]]) {
// update title with profile name
[self setNavigationTitle];
[self updateNavigationTitle];
}
if (self.isGroupConversation) {
@ -489,7 +487,16 @@ typedef enum : NSUInteger {
[self createConversationScrollButtons];
[self createHeaderViews];
[self createBackButton];
if (@available(iOS 11, *)) {
// We use the default back button from home view, which animates nicely with interactive transitions like the
// interactive pop gesture and the "slide left" for info.
} else {
// On iOS9/10 the default back button is too wide, so we use a custom back button. This doesn't animate nicely
// with interactive transitions, but has the appropriate width.
[self createBackButton];
}
[self addNotificationListeners];
[self loadDraftInCompose];
}
@ -623,7 +630,7 @@ typedef enum : NSUInteger {
[self updateDisappearingMessagesConfiguration];
[self updateBarButtonItems];
[self setNavigationTitle];
[self updateNavigationTitle];
// We want to set the initial scroll state the first time we enter the view.
if (!self.viewHasEverAppeared) {
@ -1082,7 +1089,7 @@ typedef enum : NSUInteger {
#pragma mark - Initiliazers
- (void)setNavigationTitle
- (void)updateNavigationTitle
{
NSAttributedString *name;
if (self.thread.isGroupThread) {
@ -1095,19 +1102,16 @@ typedef enum : NSUInteger {
OWSAssert(self.thread.contactIdentifier);
name = [self.contactsManager
attributedStringForConversationTitleWithPhoneIdentifier:self.thread.contactIdentifier
primaryFont:[self navigationBarTitleLabelFont]
secondaryFont:[UIFont ows_regularFontWithSize:11.f]];
primaryFont:self.headerView.titlePrimaryFont
secondaryFont:self.headerView.titleSecondaryFont];
}
self.title = nil;
if ([name isEqual:self.navigationBarTitleLabel.attributedText]) {
if ([name isEqual:self.headerView.attributedTitle]) {
return;
}
self.navigationBarTitleLabel.attributedText = name;
// Changing the title requires relayout of the nav bar contents.
[self updateBarButtonItems];
self.headerView.attributedTitle = name;
}
- (void)createHeaderViews
@ -1124,33 +1128,31 @@ typedef enum : NSUInteger {
_backButtonUnreadCountLabel.font = [UIFont systemFontOfSize:11];
_backButtonUnreadCountLabel.textAlignment = NSTextAlignmentCenter;
self.navigationBarTitleView = [ConversationHeaderView new];
self.navigationBarTitleView.userInteractionEnabled = YES;
[self.navigationBarTitleView
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(navigationTitleTapped:)]];
ConversationHeaderView *headerView =
[[ConversationHeaderView alloc] initWithThread:self.thread contactsManager:self.contactsManager];
self.headerView = headerView;
headerView.delegate = self;
self.navigationItem.titleView = headerView;
if (@available(iOS 11, *)) {
// Do nothing, we use autolayout/intrinsic content size to grow
} else {
// Request "full width" title; the navigation bar will truncate this
// to fit between the left and right buttons.
CGSize screenSize = [UIScreen mainScreen].bounds.size;
CGRect headerFrame = CGRectMake(0, 0, screenSize.width, 44);
headerView.frame = headerFrame;
}
#ifdef USE_DEBUG_UI
[self.navigationBarTitleView addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:@selector(navigationTitleLongPressed:)]];
[headerView addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:@selector(navigationTitleLongPressed:)]];
#endif
self.navigationBarTitleLabel = [UILabel new];
self.navigationBarTitleView.titleLabel = self.navigationBarTitleLabel;
self.navigationBarTitleLabel.textColor = [UIColor whiteColor];
self.navigationBarTitleLabel.font = [self navigationBarTitleLabelFont];
self.navigationBarTitleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
[self.navigationBarTitleView addSubview:self.navigationBarTitleLabel];
self.navigationBarSubtitleLabel = [UILabel new];
self.navigationBarTitleView.subtitleLabel = self.navigationBarSubtitleLabel;
[self updateNavigationBarSubtitleLabel];
[self.navigationBarTitleView addSubview:self.navigationBarSubtitleLabel];
}
- (UIFont *)navigationBarTitleLabelFont
{
return [UIFont ows_boldFontWithSize:20.f];
}
- (CGFloat)unreadCountViewDiameter
@ -1190,47 +1192,6 @@ typedef enum : NSUInteger {
- (void)updateBarButtonItems
{
// We want to leave space for the "back" button, the "timer" button, and the "call"
// button, and all of the whitespace around these views. There
// isn't a convenient way to calculate these in a navigation bar, so we just leave
// a constant amount of space which will be safe unless Apple makes radical changes
// to the appearance of the navigation bar.
int rightBarButtonItemCount = 0;
if ([self canCall]) {
rightBarButtonItemCount++;
}
if (self.disappearingMessagesConfiguration.isEnabled) {
rightBarButtonItemCount++;
}
CGFloat barButtonSize = 0;
switch (rightBarButtonItemCount) {
case 0:
barButtonSize = 70;
break;
case 1:
barButtonSize = 105;
break;
default:
OWSFail(@"%@ Unexpected number of right navbar items.", self.logTag);
// In production, fall through to the largest defined case.
case 2:
barButtonSize = 150;
break;
}
CGSize screenSize = [UIScreen mainScreen].bounds.size;
CGFloat screenWidth = MIN(screenSize.width, screenSize.height);
if (self.navigationItem.titleView != self.navigationBarTitleView) {
// Request "full width" title; the navigation bar will truncate this
// to fit between the left and right buttons.
self.navigationBarTitleView.frame = CGRectMake(0, 0, screenWidth, 44);
self.navigationItem.titleView = self.navigationBarTitleView;
} else {
// Don't reset the frame of the navigationBarTitleView every time
// this method is called or we'll gave bad frames where it will appear
// in the wrong position.
[self.navigationBarTitleView layoutSubviews];
}
if (self.userLeftGroup) {
self.navigationItem.rightBarButtonItems = @[];
return;
@ -1326,7 +1287,7 @@ typedef enum : NSUInteger {
appendAttributedString:[[NSAttributedString alloc]
initWithString:NSLocalizedString(@"GROUP_YOU_LEFT", @"")
attributes:@{
NSFontAttributeName : [UIFont ows_regularFontWithSize:9.f],
NSFontAttributeName : self.headerView.subtitleFont,
NSForegroundColorAttributeName : [UIColor colorWithWhite:0.9f alpha:1.f],
}]];
} else {
@ -1336,13 +1297,12 @@ typedef enum : NSUInteger {
@"The subtitle for the messages view title indicates that the "
@"title can be tapped to access settings for this conversation.")
attributes:@{
NSFontAttributeName : [UIFont ows_regularFontWithSize:9.f],
NSFontAttributeName : self.headerView.subtitleFont,
NSForegroundColorAttributeName : [UIColor colorWithWhite:0.9f alpha:1.f],
}]];
}
self.navigationBarSubtitleLabel.attributedText = subtitleText;
[self.navigationBarSubtitleLabel sizeToFit];
self.headerView.attributedSubtitle = subtitleText;
}
@ -3080,7 +3040,7 @@ typedef enum : NSUInteger {
}
}
}];
[self setNavigationTitle];
[self updateNavigationTitle];
}
[self updateDisappearingMessagesConfiguration];
@ -3845,13 +3805,11 @@ typedef enum : NSUInteger {
return @[];
}
#pragma mark - Event Handling
#pragma mark - ConversationHeaderViewDelegate
- (void)navigationTitleTapped:(UIGestureRecognizer *)gestureRecognizer
- (void)didTapConversationHeaderView:(ConversationHeaderView *)conversationHeaderView
{
if (gestureRecognizer.state == UIGestureRecognizerStateRecognized) {
[self showConversationSettings];
}
[self showConversationSettings];
}
#ifdef USE_DEBUG_UI

@ -273,11 +273,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
self.title = NSLocalizedString(@"HOME_VIEW_TITLE_ARCHIVE", @"Title for the home view's 'archive' mode.");
break;
}
self.navigationItem.backBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"BACK_BUTTON", @"button text for back button")
style:UIBarButtonItemStylePlain
target:nil
action:nil];
[self applyDefaultBackButton];
if ([self.traitCollection respondsToSelector:@selector(forceTouchCapability)]
&& (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable)) {
@ -287,11 +284,35 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
[self updateBarButtonItems];
}
- (void)applyDefaultBackButton
{
// 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 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 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.
NSUInteger paddingLength = 3;
NSString *paddingString = [@"" stringByPaddingToLength:paddingLength withString:@" " startingAtIndex:0];
self.navigationItem.backBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:paddingString style:UIBarButtonItemStylePlain target:nil action:nil];
}
- (void)applyArchiveBackButton
{
self.navigationItem.backBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"BACK_BUTTON", @"button text for back button")
style:UIBarButtonItemStylePlain
target:nil
action:nil];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self displayAnyUnseenUpgradeExperience];
[self applyDefaultBackButton];
}
- (void)updateBarButtonItems
@ -402,6 +423,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
__block BOOL hasAnyMessages;
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction];
@ -436,6 +458,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
}
[self checkIfEmptyView];
[self applyDefaultBackButton];
}
- (void)viewWillDisappear:(BOOL)animated
@ -979,6 +1002,10 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
{
OWSAssert(self.homeViewMode == HomeViewMode_Inbox);
// When showing archived conversations, we want to use a conventional "back" button
// to return to the "inbox" home view.
[self applyArchiveBackButton];
// Push a separate instance of this view using "archive" mode.
HomeViewController *homeView = [HomeViewController new];
homeView.homeViewMode = HomeViewMode_Archive;

@ -67,7 +67,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
private let showAllMediaButton: Bool
private let sliderEnabled: Bool
private let navItemTitleView: ConversationHeaderView!
private let headerView: UIStackView
init(initialItem: MediaGalleryItem, mediaGalleryDataSource: MediaGalleryDataSource, uiDatabaseConnection: YapDatabaseConnection, options: MediaGalleryOption) {
assert(uiDatabaseConnection.isInLongLivedReadTransaction())
@ -76,15 +76,23 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
self.sliderEnabled = options.contains(.sliderEnabled)
self.mediaGalleryDataSource = mediaGalleryDataSource
let headerView = ConversationHeaderView()
self.navItemTitleView = headerView
let kSpacingBetweenItems: CGFloat = 20
let headerView = UIStackView()
self.headerView = headerView
super.init(transitionStyle: .scroll,
navigationOrientation: .horizontal,
options: [UIPageViewControllerOptionInterPageSpacingKey: kSpacingBetweenItems])
// needed for proper layout on iOS9/10
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.axis = .vertical
headerView.alignment = .center
headerView.addArrangedSubview(headerNameLabel)
headerView.addArrangedSubview(headerDateLabel)
self.dataSource = self
self.delegate = self
@ -120,13 +128,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
let backButton = OWSViewController.createOWSBackButton(withTarget: self, selector: #selector(didPressDismissButton))
self.navigationItem.leftBarButtonItem = backButton
navItemTitleView.titleLabel = headerNameLabel
navItemTitleView.subtitleLabel = headerDateLabel
navItemTitleView.addSubview(headerNameLabel)
navItemTitleView.addSubview(headerDateLabel)
navItemTitleView.frame = CGRect(origin: .zero, size: CGSize(width: 150, height: 35))
navItemTitleView.layoutSubviews()
self.navigationItem.titleView = navItemTitleView
self.navigationItem.titleView = headerView
self.updateTitle()
if showAllMediaButton {

@ -79,17 +79,29 @@ class ReminderView: UIView {
self.addSubview(container)
container.autoPinWidthToSuperview(withMargin: 16)
container.autoPinHeightToSuperview(withMargin: 16)
switch (mode) {
case .nag:
container.autoPinHeightToSuperview(withMargin: 16)
case .explanation:
container.autoPinHeightToSuperview(withMargin: 12)
}
// Margin: top and bottom 12 left and right 16.
// Label
label.font = UIFont.ows_regularFont(withSize: 14)
switch (mode) {
case .nag:
label.font = UIFont.ows_regularFont(withSize: 14)
case .explanation:
label.font = UIFont.ows_dynamicTypeSubheadline
}
container.addSubview(label)
label.textColor = UIColor.black.withAlphaComponent(0.9)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.autoPinEdge(toSuperviewEdge: .top)
label.autoPinLeadingToSuperviewMargin()
label.autoPinEdge(toSuperviewEdge: .top)
label.autoPinEdge(toSuperviewEdge: .bottom)
label.textColor = UIColor.black.withAlphaComponent(0.9)
guard mode == .nag else {
label.autoPinTrailingToSuperviewMargin()

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import UIKit
@ -8,7 +8,7 @@ import UIKit
public class AvatarImageView: UIImageView {
public init() {
super.init(frame: CGRect.zero)
super.init(frame: .zero)
self.configureView()
}
@ -28,6 +28,8 @@ public class AvatarImageView: UIImageView {
}
func configureView() {
self.autoPinToSquareAspectRatio()
self.layer.minificationFilter = kCAFilterTrilinear
self.layer.magnificationFilter = kCAFilterTrilinear
self.layer.borderWidth = 0.5
@ -40,3 +42,117 @@ public class AvatarImageView: UIImageView {
self.layer.cornerRadius = self.frame.size.width / 2
}
}
/// Avatar View which updates itself as necessary when the profile, contact, or group picture changes.
@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)")
// 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.
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 == changedRecipientId 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)
}
}

@ -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