Update conversation view UI to reflect unread state.
This commit is contained in:
parent
b3d9363961
commit
5ef9d53c91
|
@ -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 */,
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue