Update conversation view UI to reflect unread state.

This commit is contained in:
Matthew Chen 2017-11-21 16:23:42 -05:00
parent b3d9363961
commit 5ef9d53c91
4 changed files with 178 additions and 51 deletions

View File

@ -21,6 +21,7 @@
34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; };
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; };
343A65951FC47D5E000477A1 /* DebugUISyncMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A65941FC47D5E000477A1 /* DebugUISyncMessages.m */; };
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A65961FC4CFE6000477A1 /* ConversationScrollButton.m */; };
343D3D9B1E9283F100165CA4 /* BlockListUIUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 343D3D9A1E9283F100165CA4 /* BlockListUIUtils.m */; };
344F2F671E57A932000D9322 /* UIViewController+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 344F2F661E57A932000D9322 /* UIViewController+OWS.m */; };
34533F181EA8D2070006114F /* OWSAudioAttachmentPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34533F171EA8D2070006114F /* OWSAudioAttachmentPlayer.m */; };
@ -424,6 +425,8 @@
34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = "<group>"; };
343A65931FC47D5D000477A1 /* DebugUISyncMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUISyncMessages.h; sourceTree = "<group>"; };
343A65941FC47D5E000477A1 /* DebugUISyncMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUISyncMessages.m; sourceTree = "<group>"; };
343A65961FC4CFE6000477A1 /* ConversationScrollButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversationScrollButton.m; sourceTree = "<group>"; };
343A65971FC4CFE7000477A1 /* ConversationScrollButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConversationScrollButton.h; sourceTree = "<group>"; };
343D3D991E9283F100165CA4 /* BlockListUIUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlockListUIUtils.h; sourceTree = "<group>"; };
343D3D9A1E9283F100165CA4 /* BlockListUIUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlockListUIUtils.m; sourceTree = "<group>"; };
34491FC11FB0F78500B3E5A3 /* my */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = my; path = translations/my.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -972,6 +975,8 @@
34D1F0681F8678AA0066283D /* ConversationInputTextView.m */,
34D1F0691F8678AA0066283D /* ConversationInputToolbar.h */,
34D1F06A1F8678AA0066283D /* ConversationInputToolbar.m */,
343A65971FC4CFE7000477A1 /* ConversationScrollButton.h */,
343A65961FC4CFE6000477A1 /* ConversationScrollButton.m */,
34D1F06D1F8678AA0066283D /* ConversationViewController.h */,
34D1F06E1F8678AA0066283D /* ConversationViewController.m */,
34D1F06F1F8678AA0066283D /* ConversationViewItem.h */,
@ -2210,6 +2215,7 @@
45CD81F21DC03A22004C9430 /* OWSLogger.m in Sources */,
4542F0941EB9372700C7EE92 /* SystemContactsFetcher.swift in Sources */,
B60C16651988999D00E97A6C /* VersionMigrations.m in Sources */,
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */,
B97940271832BD2400BD66CB /* UIUtil.m in Sources */,
34CE88ED1F3237260098030F /* ProfileFetcherJob.swift in Sources */,
34B3F8791E8DF1700035BE1A /* CountryCodeViewController.m in Sources */,

View File

@ -0,0 +1,15 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface ConversationScrollButton : UIButton
@property (nonatomic) BOOL hasUnreadMessages;
- (nullable instancetype)initWithIconText:(NSString *)iconText;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,97 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "ConversationScrollButton.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
NS_ASSUME_NONNULL_BEGIN
@interface ConversationScrollButton ()
@property (nonatomic) NSString *iconText;
@property (nonatomic) UILabel *iconLabel;
@property (nonatomic) UIView *circleView;
@end
#pragma mark -
@implementation ConversationScrollButton
- (nullable instancetype)initWithIconText:(NSString *)iconText
{
self = [super initWithFrame:CGRectMake(0, 0, self.buttonSize, self.buttonSize)];
if (!self) {
return self;
}
self.iconText = iconText;
[self createContents];
return self;
}
- (CGFloat)circleSize
{
return ScaleFromIPhone5To7Plus(35.f, 40.f);
}
- (CGFloat)buttonSize
{
return self.circleSize + 2 * 15.f;
}
- (void)createContents
{
UILabel *iconLabel = [UILabel new];
self.iconLabel = iconLabel;
iconLabel.userInteractionEnabled = NO;
UIView *circleView = [UIView new];
self.circleView = circleView;
circleView.backgroundColor = [UIColor colorWithWhite:0.95f alpha:1.f];
circleView.userInteractionEnabled = NO;
circleView.layer.cornerRadius = self.circleSize * 0.5f;
circleView.layer.shadowColor = [UIColor colorWithWhite:0.5f alpha:1.f].CGColor;
circleView.layer.shadowOffset = CGSizeMake(+1.f, +2.f);
circleView.layer.shadowRadius = 1.5f;
circleView.layer.shadowOpacity = 0.35f;
[circleView autoSetDimension:ALDimensionWidth toSize:self.circleSize];
[circleView autoSetDimension:ALDimensionHeight toSize:self.circleSize];
[self addSubview:circleView];
[self addSubview:iconLabel];
[circleView autoCenterInSuperview];
[iconLabel autoCenterInSuperview];
[self updateColors];
}
- (void)setHasUnreadMessages:(BOOL)hasUnreadMessages
{
_hasUnreadMessages = hasUnreadMessages;
[self updateColors];
}
- (void)updateColors
{
self.circleView.backgroundColor
= (self.hasUnreadMessages ? [UIColor ows_materialBlueColor] : [UIColor colorWithWhite:0.95f alpha:1.f]);
self.iconLabel.attributedText = [[NSAttributedString alloc]
initWithString:self.iconText
attributes:@{
NSFontAttributeName : [UIFont ows_fontAwesomeFont:self.circleSize * 0.8f],
NSForegroundColorAttributeName :
(self.hasUnreadMessages ? [UIColor whiteColor] : [UIColor ows_materialBlueColor]),
NSBaselineOffsetAttributeName : @(-0.5f),
}];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -11,6 +11,7 @@
#import "ConversationHeaderView.h"
#import "ConversationInputTextView.h"
#import "ConversationInputToolbar.h"
#import "ConversationScrollButton.h"
#import "ConversationViewCell.h"
#import "ConversationViewItem.h"
#import "ConversationViewLayout.h"
@ -214,9 +215,9 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
@property (nonatomic, readonly) BOOL isGroupConversation;
@property (nonatomic) BOOL isUserScrolling;
@property (nonatomic) UIView *scrollDownButton;
@property (nonatomic) ConversationScrollButton *scrollDownButton;
#ifdef DEBUG
@property (nonatomic) UIView *scrollUpButton;
@property (nonatomic) ConversationScrollButton *scrollUpButton;
#endif
@property (nonatomic) BOOL isViewVisible;
@ -225,6 +226,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
@property (nonatomic) BOOL viewHasEverAppeared;
@property (nonatomic) BOOL wasScrolledToBottomBeforeKeyboardShow;
@property (nonatomic) BOOL wasScrolledToBottomBeforeLayoutChange;
@property (nonatomic) BOOL hasUnreadMessages;
@end
@ -472,7 +474,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
[self registerCellClasses];
[self createScrollButtons];
[self createConversationScrollButtons];
[self createHeaderViews];
[self createBackButton];
[self addNotificationListeners];
@ -596,12 +598,13 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
[self updateBarButtonItems];
[self setNavigationTitle];
[self updateLastVisibleTimestamp];
// We want to set the initial scroll state the first time we enter the view.
if (!self.viewHasEverAppeared) {
[self scrollToDefaultPosition];
}
[self updateLastVisibleTimestamp];
}
- (NSIndexPath *_Nullable)indexPathOfUnreadMessagesIndicator
@ -2222,51 +2225,41 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
}
}
- (void)createScrollButtons
- (void)createConversationScrollButtons
{
self.scrollDownButton = [self createScrollButton:@"\uf103" selector:@selector(scrollDownButtonTapped)];
self.scrollDownButton = [[ConversationScrollButton alloc] initWithIconText:@"\uf103"];
[self.scrollDownButton addTarget:self
action:@selector(scrollDownButtonTapped)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.scrollDownButton];
[self.scrollDownButton autoSetDimension:ALDimensionWidth toSize:self.scrollDownButton.width];
[self.scrollDownButton autoSetDimension:ALDimensionHeight toSize:self.scrollDownButton.width];
[self.scrollDownButton autoPinEdge:ALEdgeBottom toEdge:ALEdgeTop ofView:self.inputToolbar];
[self.scrollDownButton autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
#ifdef DEBUG
self.scrollUpButton = [self createScrollButton:@"\uf102" selector:@selector(scrollUpButtonTapped)];
self.scrollUpButton = [[ConversationScrollButton alloc] initWithIconText:@"\uf102"];
[self.scrollUpButton addTarget:self
action:@selector(scrollUpButtonTapped)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.scrollUpButton];
[self.scrollUpButton autoSetDimension:ALDimensionWidth toSize:self.scrollUpButton.width];
[self.scrollUpButton autoSetDimension:ALDimensionHeight toSize:self.scrollUpButton.width];
[self.scrollUpButton autoPinToTopLayoutGuideOfViewController:self withInset:0];
[self.scrollUpButton autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
#endif
}
- (UIView *)createScrollButton:(NSString *)label selector:(SEL)selector
- (void)setHasUnreadMessages:(BOOL)hasUnreadMessages
{
const CGFloat kCircleSize = ScaleFromIPhone5To7Plus(35.f, 40.f);
if (_hasUnreadMessages == hasUnreadMessages) {
return;
}
UILabel *iconLabel = [UILabel new];
iconLabel.attributedText =
[[NSAttributedString alloc] initWithString:label
attributes:@{
NSFontAttributeName : [UIFont ows_fontAwesomeFont:kCircleSize * 0.8f],
NSForegroundColorAttributeName : [UIColor ows_materialBlueColor],
NSBaselineOffsetAttributeName : @(-0.5f),
}];
iconLabel.userInteractionEnabled = NO;
_hasUnreadMessages = hasUnreadMessages;
UIView *circleView = [UIView new];
circleView.backgroundColor = [UIColor colorWithWhite:0.95f alpha:1.f];
circleView.userInteractionEnabled = NO;
circleView.layer.cornerRadius = kCircleSize * 0.5f;
circleView.layer.shadowColor = [UIColor colorWithWhite:0.5f alpha:1.f].CGColor;
circleView.layer.shadowOffset = CGSizeMake(+1.f, +2.f);
circleView.layer.shadowRadius = 1.5f;
circleView.layer.shadowOpacity = 0.35f;
[circleView autoSetDimension:ALDimensionWidth toSize:kCircleSize];
[circleView autoSetDimension:ALDimensionHeight toSize:kCircleSize];
const CGFloat kButtonSize = kCircleSize + 2 * 15.f;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(0, 0, kButtonSize, kButtonSize);
[self.view addSubview:button];
[button addSubview:circleView];
[button addSubview:iconLabel];
[circleView autoCenterInSuperview];
[iconLabel autoCenterInSuperview];
return button;
self.scrollDownButton.hasUnreadMessages = hasUnreadMessages;
[self ensureDynamicInteractions];
}
- (void)scrollDownButtonTapped
@ -2330,11 +2323,6 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
if (shouldShowScrollDownButton) {
self.scrollDownButton.hidden = NO;
self.scrollDownButton.frame
= CGRectMake((self.view.isRTL ? 0.f : self.scrollDownButton.superview.width - self.scrollDownButton.width),
self.inputToolbar.top - self.scrollDownButton.height,
self.scrollDownButton.width,
self.scrollDownButton.height);
} else {
self.scrollDownButton.hidden = YES;
}
@ -2343,11 +2331,6 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
BOOL shouldShowScrollUpButton = self.collectionView.contentOffset.y > 0;
if (shouldShowScrollUpButton) {
self.scrollUpButton.hidden = NO;
self.scrollUpButton.frame
= CGRectMake((self.view.isRTL ? 0.f : self.scrollUpButton.superview.width - self.scrollUpButton.width),
0,
self.scrollUpButton.width,
self.scrollUpButton.height);
} else {
self.scrollUpButton.hidden = YES;
}
@ -3407,6 +3390,23 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
return [self viewItemForIndex:lastVisibleIndexPath.row];
}
// In the case where we explicitly scroll to bottom, we want to synchronously
// update the UI to reflect that, since the "mark as read" logic is asynchronous
// and won't update the UI state immediately.
- (void)didScrollToBottom
{
ConversationViewItem *_Nullable lastVisibleViewItem = [self.viewItems lastObject];
if (lastVisibleViewItem) {
uint64_t lastVisibleTimestamp = lastVisibleViewItem.interaction.timestampForSorting;
self.lastVisibleTimestamp = MAX(self.lastVisibleTimestamp, lastVisibleTimestamp);
}
self.scrollDownButton.hidden = YES;
self.hasUnreadMessages = NO;
}
- (void)updateLastVisibleTimestamp
{
ConversationViewItem *_Nullable lastVisibleViewItem = [self lastVisibleViewItem];
@ -3416,6 +3416,13 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
}
[self ensureScrollDownButton];
__block NSUInteger numberOfUnreadMessages;
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
numberOfUnreadMessages =
[[transaction ext:TSUnreadDatabaseViewExtensionName] numberOfItemsInGroup:self.thread.uniqueId];
}];
self.hasUnreadMessages = numberOfUnreadMessages > 0;
}
- (void)updateLastVisibleTimestamp:(uint64_t)timestamp
@ -3697,6 +3704,8 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
CGFloat contentHeight = self.safeContentHeight;
CGFloat dstY = MAX(0, contentHeight - self.collectionView.height);
[self.collectionView setContentOffset:CGPointMake(0, dstY) animated:animated];
[self didScrollToBottom];
}
#pragma mark - UIScrollViewDelegate