Rework unread indicators.
This commit is contained in:
parent
1ce147e945
commit
8d72bb032e
@ -161,6 +161,7 @@
|
||||
3478506C1FD9B78A007B8332 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */; };
|
||||
347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 3478506F1FDAEB16007B8332 /* OWSUserProfile.m */; };
|
||||
347850721FDAEB17007B8332 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 347850701FDAEB16007B8332 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
348570A820F67575004FF32B /* OWSMessageHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 348570A620F67574004FF32B /* OWSMessageHeaderView.m */; };
|
||||
348BB254209CD4B80047AEC2 /* ContactFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB253209CD4B80047AEC2 /* ContactFieldView.swift */; };
|
||||
348BB25A209CF8E50047AEC2 /* TappableStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB258209CF8E40047AEC2 /* TappableStackView.swift */; };
|
||||
348BB25B209CF8E50047AEC2 /* TappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB259209CF8E50047AEC2 /* TappableView.swift */; };
|
||||
@ -180,6 +181,8 @@
|
||||
34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */; };
|
||||
34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */; };
|
||||
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */; };
|
||||
34B6D27420F664C900765BE2 /* OWSUnreadIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 34B6D27220F664C800765BE2 /* OWSUnreadIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
34B6D27520F664C900765BE2 /* OWSUnreadIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B6D27320F664C800765BE2 /* OWSUnreadIndicator.m */; };
|
||||
34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2A1F74C12700D7438D /* DebugUIStress.m */; };
|
||||
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */; };
|
||||
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */; };
|
||||
@ -208,7 +211,6 @@
|
||||
34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */; };
|
||||
34D1F0AE1F867BFC0066283D /* OWSMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A21F867BFC0066283D /* OWSMessageCell.m */; };
|
||||
34D1F0B01F867BFC0066283D /* OWSSystemMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A61F867BFC0066283D /* OWSSystemMessageCell.m */; };
|
||||
34D1F0B11F867BFC0066283D /* OWSUnreadIndicatorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A81F867BFC0066283D /* OWSUnreadIndicatorCell.m */; };
|
||||
34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0B31F86D31D0066283D /* ConversationCollectionView.m */; };
|
||||
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0B61F87F8850066283D /* OWSGenericAttachmentView.m */; };
|
||||
34D1F0BA1F8800D90066283D /* OWSAudioMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0B91F8800D90066283D /* OWSAudioMessageView.m */; };
|
||||
@ -775,6 +777,8 @@
|
||||
347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = "<group>"; };
|
||||
3478506F1FDAEB16007B8332 /* OWSUserProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUserProfile.m; sourceTree = "<group>"; };
|
||||
347850701FDAEB16007B8332 /* OWSUserProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUserProfile.h; sourceTree = "<group>"; };
|
||||
348570A620F67574004FF32B /* OWSMessageHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageHeaderView.m; sourceTree = "<group>"; };
|
||||
348570A720F67574004FF32B /* OWSMessageHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageHeaderView.h; sourceTree = "<group>"; };
|
||||
348BB253209CD4B80047AEC2 /* ContactFieldView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContactFieldView.swift; path = SignalMessaging/attachments/ContactFieldView.swift; sourceTree = SOURCE_ROOT; };
|
||||
348BB258209CF8E40047AEC2 /* TappableStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableStackView.swift; path = SignalMessaging/Views/TappableStackView.swift; sourceTree = SOURCE_ROOT; };
|
||||
348BB259209CF8E50047AEC2 /* TappableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableView.swift; path = SignalMessaging/Views/TappableView.swift; sourceTree = SOURCE_ROOT; };
|
||||
@ -808,6 +812,8 @@
|
||||
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalsNavigationController.m; sourceTree = "<group>"; };
|
||||
34B3F89D1E8DF5490035BE1A /* OWSTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSTableViewController.h; sourceTree = "<group>"; };
|
||||
34B3F89E1E8DF5490035BE1A /* OWSTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSTableViewController.m; sourceTree = "<group>"; };
|
||||
34B6D27220F664C800765BE2 /* OWSUnreadIndicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUnreadIndicator.h; sourceTree = "<group>"; };
|
||||
34B6D27320F664C800765BE2 /* OWSUnreadIndicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUnreadIndicator.m; sourceTree = "<group>"; };
|
||||
34BECE291F74C12700D7438D /* DebugUIStress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIStress.h; sourceTree = "<group>"; };
|
||||
34BECE2A1F74C12700D7438D /* DebugUIStress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIStress.m; sourceTree = "<group>"; };
|
||||
34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerViewController.swift; sourceTree = "<group>"; };
|
||||
@ -857,8 +863,6 @@
|
||||
34D1F0A21F867BFC0066283D /* OWSMessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageCell.m; sourceTree = "<group>"; };
|
||||
34D1F0A51F867BFC0066283D /* OWSSystemMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSystemMessageCell.h; sourceTree = "<group>"; };
|
||||
34D1F0A61F867BFC0066283D /* OWSSystemMessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSystemMessageCell.m; sourceTree = "<group>"; };
|
||||
34D1F0A71F867BFC0066283D /* OWSUnreadIndicatorCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUnreadIndicatorCell.h; sourceTree = "<group>"; };
|
||||
34D1F0A81F867BFC0066283D /* OWSUnreadIndicatorCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUnreadIndicatorCell.m; sourceTree = "<group>"; };
|
||||
34D1F0B21F86D31D0066283D /* ConversationCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConversationCollectionView.h; sourceTree = "<group>"; };
|
||||
34D1F0B31F86D31D0066283D /* ConversationCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversationCollectionView.m; sourceTree = "<group>"; };
|
||||
34D1F0B51F87F8850066283D /* OWSGenericAttachmentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSGenericAttachmentView.h; sourceTree = "<group>"; };
|
||||
@ -1467,6 +1471,8 @@
|
||||
34641E172088D7E900E2EDE5 /* OWSScreenLock.swift */,
|
||||
34480B4F1FD0A7A300BC14EF /* OWSScrubbingLogFormatter.h */,
|
||||
34480B511FD0A7A400BC14EF /* OWSScrubbingLogFormatter.m */,
|
||||
34B6D27220F664C800765BE2 /* OWSUnreadIndicator.h */,
|
||||
34B6D27320F664C800765BE2 /* OWSUnreadIndicator.m */,
|
||||
34641E1120878FB000E2EDE5 /* OWSWindowManager.h */,
|
||||
34641E1020878FAF00E2EDE5 /* OWSWindowManager.m */,
|
||||
45360B8C1F9521F800FA666C /* Searcher.swift */,
|
||||
@ -1772,6 +1778,8 @@
|
||||
34D1F0A21F867BFC0066283D /* OWSMessageCell.m */,
|
||||
34D920E520E179C100D51158 /* OWSMessageFooterView.h */,
|
||||
34D920E620E179C200D51158 /* OWSMessageFooterView.m */,
|
||||
348570A720F67574004FF32B /* OWSMessageHeaderView.h */,
|
||||
348570A620F67574004FF32B /* OWSMessageHeaderView.m */,
|
||||
34DBF000206BD5A400025978 /* OWSMessageTextView.h */,
|
||||
34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */,
|
||||
3427C64120F500DE00EEC730 /* OWSMessageTimerView.h */,
|
||||
@ -1780,8 +1788,6 @@
|
||||
34277A5C20751BDC006049F2 /* OWSQuotedMessageView.m */,
|
||||
34D1F0A51F867BFC0066283D /* OWSSystemMessageCell.h */,
|
||||
34D1F0A61F867BFC0066283D /* OWSSystemMessageCell.m */,
|
||||
34D1F0A71F867BFC0066283D /* OWSUnreadIndicatorCell.h */,
|
||||
34D1F0A81F867BFC0066283D /* OWSUnreadIndicatorCell.m */,
|
||||
);
|
||||
path = Cells;
|
||||
sourceTree = "<group>";
|
||||
@ -2456,6 +2462,7 @@
|
||||
451F8A491FD715CF005CB9DA /* OWSAvatarBuilder.h in Headers */,
|
||||
346129951FD1E30000532771 /* OWSDatabaseMigration.h in Headers */,
|
||||
45194F961FD7226300333B2C /* SelectThreadViewController.h in Headers */,
|
||||
34B6D27420F664C900765BE2 /* OWSUnreadIndicator.h in Headers */,
|
||||
346129B41FD1F7E800532771 /* OWSProfileManager.h in Headers */,
|
||||
34D2015120DC160E00A6FD3A /* ContactCellView.h in Headers */,
|
||||
346129FA1FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.h in Headers */,
|
||||
@ -3165,6 +3172,7 @@
|
||||
34382266209A4E400094FEB7 /* ContactShareApprovalViewController.swift in Sources */,
|
||||
4503F1C3204711D300CEE724 /* OWS107LegacySounds.m in Sources */,
|
||||
3438226A209B63500094FEB7 /* EditContactShareNameViewController.swift in Sources */,
|
||||
34B6D27520F664C900765BE2 /* OWSUnreadIndicator.m in Sources */,
|
||||
346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */,
|
||||
4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */,
|
||||
4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */,
|
||||
@ -3221,6 +3229,7 @@
|
||||
34D1F0871F8678AA0066283D /* ConversationViewItem.m in Sources */,
|
||||
B6DA6B071B8A2F9A00CA6F98 /* AppStoreRating.m in Sources */,
|
||||
451A13B11E13DED2000A50FD /* CallNotificationsAdapter.swift in Sources */,
|
||||
348570A820F67575004FF32B /* OWSMessageHeaderView.m in Sources */,
|
||||
450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */,
|
||||
34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */,
|
||||
340FC8C7204DE64D007AEB0F /* OWSBackupAPI.swift in Sources */,
|
||||
@ -3345,7 +3354,6 @@
|
||||
76EB054018170B33006006FC /* AppDelegate.m in Sources */,
|
||||
34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */,
|
||||
340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */,
|
||||
34D1F0B11F867BFC0066283D /* OWSUnreadIndicatorCell.m in Sources */,
|
||||
340FC8B5204DAC8D007AEB0F /* AboutTableViewController.m in Sources */,
|
||||
34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */,
|
||||
340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */,
|
||||
|
||||
@ -8,8 +8,6 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern const CGFloat OWSMessageCellDateHeaderVMargin;
|
||||
|
||||
@interface OWSMessageCell : ConversationViewCell
|
||||
|
||||
@property (nonatomic, readonly) OWSMessageBubbleView *messageBubbleView;
|
||||
|
||||
@ -5,21 +5,19 @@
|
||||
#import "OWSMessageCell.h"
|
||||
#import "OWSContactAvatarBuilder.h"
|
||||
#import "OWSMessageBubbleView.h"
|
||||
#import "OWSMessageHeaderView.h"
|
||||
#import "Signal-Swift.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
const CGFloat OWSMessageCellDateHeaderVMargin = 23;
|
||||
|
||||
@interface OWSMessageCell ()
|
||||
|
||||
// The nullable properties are created as needed.
|
||||
// The non-nullable properties are so frequently used that it's easier
|
||||
// to always keep one around.
|
||||
|
||||
@property (nonatomic) OWSMessageHeaderView *headerView;
|
||||
@property (nonatomic) OWSMessageBubbleView *messageBubbleView;
|
||||
@property (nonatomic) UIView *dateHeaderView;
|
||||
@property (nonatomic) UILabel *dateHeaderLabel;
|
||||
@property (nonatomic) AvatarImageView *avatarView;
|
||||
@property (nonatomic, nullable) UIImageView *sendFailureBadgeView;
|
||||
|
||||
@ -55,15 +53,7 @@ const CGFloat OWSMessageCellDateHeaderVMargin = 23;
|
||||
self.messageBubbleView = [OWSMessageBubbleView new];
|
||||
[self.contentView addSubview:self.messageBubbleView];
|
||||
|
||||
self.dateHeaderLabel = [UILabel new];
|
||||
self.dateHeaderLabel.font = self.dateHeaderFont;
|
||||
self.dateHeaderLabel.textAlignment = NSTextAlignmentCenter;
|
||||
self.dateHeaderLabel.textColor = [UIColor ows_light60Color];
|
||||
|
||||
self.dateHeaderView = [UIView new];
|
||||
self.dateHeaderView.layoutMargins = UIEdgeInsetsMake(0, 0, OWSMessageCellDateHeaderVMargin, 0);
|
||||
[self.dateHeaderView addSubview:self.dateHeaderLabel];
|
||||
[self.dateHeaderLabel autoPinToSuperviewMargins];
|
||||
self.headerView = [OWSMessageHeaderView new];
|
||||
|
||||
self.avatarView = [[AvatarImageView alloc] init];
|
||||
[self.avatarView autoSetDimension:ALDimensionWidth toSize:self.avatarSize];
|
||||
@ -153,8 +143,24 @@ const CGFloat OWSMessageCellDateHeaderVMargin = 23;
|
||||
[self.messageBubbleView configureViews];
|
||||
[self.messageBubbleView loadContent];
|
||||
|
||||
// Update label fonts to honor dynamic type size.
|
||||
self.dateHeaderLabel.font = self.dateHeaderFont;
|
||||
if (self.viewItem.hasCellHeader) {
|
||||
CGFloat headerHeight =
|
||||
[self.headerView measureWithConversationViewItem:self.viewItem conversationStyle:self.conversationStyle]
|
||||
.height;
|
||||
[self.headerView loadForDisplayWithViewItem:self.viewItem conversationStyle:self.conversationStyle];
|
||||
[self.contentView addSubview:self.headerView];
|
||||
[self.viewConstraints addObjectsFromArray:@[
|
||||
[self.headerView autoSetDimension:ALDimensionHeight toSize:headerHeight],
|
||||
[self.headerView autoPinLeadingToSuperviewMarginWithInset:self.conversationStyle.fullWidthGutterLeading],
|
||||
[self.headerView autoPinTrailingToSuperviewMarginWithInset:self.conversationStyle.fullWidthGutterTrailing],
|
||||
[self.headerView autoPinEdgeToSuperviewEdge:ALEdgeTop],
|
||||
[self.messageBubbleView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.headerView],
|
||||
]];
|
||||
} else {
|
||||
[self.viewConstraints addObjectsFromArray:@[
|
||||
[self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeTop],
|
||||
]];
|
||||
}
|
||||
|
||||
if (self.isIncoming) {
|
||||
[self.viewConstraints addObjectsFromArray:@[
|
||||
@ -203,8 +209,6 @@ const CGFloat OWSMessageCellDateHeaderVMargin = 23;
|
||||
}
|
||||
}
|
||||
|
||||
[self updateDateHeader];
|
||||
|
||||
if ([self updateAvatarView]) {
|
||||
CGFloat avatarBottomMargin = round(self.conversationStyle.lastTextLineAxis - self.avatarSize * 0.5f);
|
||||
[self.viewConstraints addObjectsFromArray:@[
|
||||
@ -251,40 +255,6 @@ const CGFloat OWSMessageCellDateHeaderVMargin = 23;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateDateHeader
|
||||
{
|
||||
OWSAssert(self.conversationStyle);
|
||||
|
||||
if (self.viewItem.shouldShowDate) {
|
||||
|
||||
self.dateHeaderLabel.font = self.dateHeaderFont;
|
||||
self.dateHeaderLabel.textColor = self.conversationStyle.dateBreakTextColor;
|
||||
|
||||
NSDate *date = self.viewItem.interaction.dateForSorting;
|
||||
NSString *dateString = [DateUtil formatDateForConversationDateBreaks:date];
|
||||
self.dateHeaderLabel.text = dateString.localizedUppercaseString;
|
||||
|
||||
[self.contentView addSubview:self.dateHeaderView];
|
||||
[self.viewConstraints addObjectsFromArray:@[
|
||||
[self.dateHeaderView
|
||||
autoPinLeadingToSuperviewMarginWithInset:self.conversationStyle.fullWidthGutterLeading],
|
||||
[self.dateHeaderView
|
||||
autoPinTrailingToSuperviewMarginWithInset:self.conversationStyle.fullWidthGutterTrailing],
|
||||
[self.dateHeaderView autoPinEdgeToSuperviewEdge:ALEdgeTop],
|
||||
[self.messageBubbleView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.dateHeaderView],
|
||||
]];
|
||||
} else {
|
||||
[self.viewConstraints addObjectsFromArray:@[
|
||||
[self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeTop],
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIFont *)dateHeaderFont
|
||||
{
|
||||
return UIFont.ows_dynamicTypeCaption1Font;
|
||||
}
|
||||
|
||||
#pragma mark - Avatar
|
||||
|
||||
// Returns YES IFF the avatar view is appropriate and configured.
|
||||
@ -376,7 +346,11 @@ const CGFloat OWSMessageCellDateHeaderVMargin = 23;
|
||||
|
||||
OWSAssert(cellSize.width > 0 && cellSize.height > 0);
|
||||
|
||||
cellSize.height += self.dateHeaderHeight;
|
||||
if (self.viewItem.hasCellHeader) {
|
||||
cellSize.height +=
|
||||
[self.headerView measureWithConversationViewItem:self.viewItem conversationStyle:self.conversationStyle]
|
||||
.height;
|
||||
}
|
||||
|
||||
if (self.shouldHaveSendFailureBadge) {
|
||||
cellSize.width += self.sendFailureBadgeSize + self.sendFailureBadgeSpacing;
|
||||
@ -387,16 +361,6 @@ const CGFloat OWSMessageCellDateHeaderVMargin = 23;
|
||||
return cellSize;
|
||||
}
|
||||
|
||||
- (CGFloat)dateHeaderHeight
|
||||
{
|
||||
if (self.viewItem.shouldShowDate) {
|
||||
CGFloat textHeight = self.dateHeaderFont.lineHeight;
|
||||
return (CGFloat)ceil(textHeight + OWSMessageCellDateHeaderVMargin);
|
||||
} else {
|
||||
return 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Reuse
|
||||
|
||||
- (void)prepareForReuse
|
||||
@ -409,7 +373,7 @@ const CGFloat OWSMessageCellDateHeaderVMargin = 23;
|
||||
[self.messageBubbleView prepareForReuse];
|
||||
[self.messageBubbleView unloadContent];
|
||||
|
||||
[self.dateHeaderView removeFromSuperview];
|
||||
[self.headerView removeFromSuperview];
|
||||
|
||||
self.avatarView.image = nil;
|
||||
[self.avatarView removeFromSuperview];
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
extern const CGFloat OWSMessageHeaderViewDateHeaderVMargin;
|
||||
|
||||
@class ConversationStyle;
|
||||
@class ConversationViewItem;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSMessageHeaderView : UIStackView
|
||||
|
||||
- (void)loadForDisplayWithViewItem:(ConversationViewItem *)viewItem
|
||||
conversationStyle:(ConversationStyle *)conversationStyle;
|
||||
|
||||
- (CGSize)measureWithConversationViewItem:(ConversationViewItem *)viewItem
|
||||
conversationStyle:(ConversationStyle *)conversationStyle;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -0,0 +1,183 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSMessageHeaderView.h"
|
||||
#import "ConversationViewItem.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <SignalMessaging/OWSUnreadIndicator.h>
|
||||
#import <SignalMessaging/UIColor+OWS.h>
|
||||
#import <SignalMessaging/UIFont+OWS.h>
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
const CGFloat OWSMessageHeaderViewDateHeaderVMargin = 23;
|
||||
|
||||
@interface OWSMessageHeaderView ()
|
||||
|
||||
@property (nonatomic) UILabel *titleLabel;
|
||||
@property (nonatomic) UILabel *subtitleLabel;
|
||||
@property (nonatomic) UIView *strokeView;
|
||||
@property (nonatomic) NSArray<NSLayoutConstraint *> *layoutConstraints;
|
||||
@property (nonatomic) UIStackView *stackView;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSMessageHeaderView
|
||||
|
||||
// `[UIView init]` invokes `[self initWithFrame:...]`.
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self commontInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)commontInit
|
||||
{
|
||||
OWSAssert(!self.titleLabel);
|
||||
|
||||
self.layoutMargins = UIEdgeInsetsZero;
|
||||
|
||||
// Intercept touches.
|
||||
// Date breaks and unread indicators are not interactive.
|
||||
self.userInteractionEnabled = YES;
|
||||
|
||||
self.strokeView = [UIView new];
|
||||
[self.strokeView setContentHuggingHigh];
|
||||
|
||||
self.titleLabel = [UILabel new];
|
||||
self.titleLabel.textColor = [UIColor ows_light90Color];
|
||||
self.titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
self.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
|
||||
self.subtitleLabel = [UILabel new];
|
||||
self.subtitleLabel.textColor = [UIColor ows_light90Color];
|
||||
// The subtitle may wrap to a second line.
|
||||
self.subtitleLabel.numberOfLines = 0;
|
||||
self.subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
self.subtitleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
|
||||
self.stackView = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
self.strokeView,
|
||||
self.titleLabel,
|
||||
self.subtitleLabel,
|
||||
]];
|
||||
self.stackView.axis = NSTextLayoutOrientationVertical;
|
||||
self.stackView.spacing = 2;
|
||||
[self addSubview:self.stackView];
|
||||
}
|
||||
|
||||
- (void)loadForDisplayWithViewItem:(ConversationViewItem *)viewItem
|
||||
conversationStyle:(ConversationStyle *)conversationStyle
|
||||
{
|
||||
OWSAssert(viewItem);
|
||||
OWSAssert(conversationStyle);
|
||||
|
||||
[self configureLabelsWithViewItem:viewItem];
|
||||
|
||||
CGFloat strokeThickness = [self strokeThicknessWithViewItem:viewItem];
|
||||
self.strokeView.layer.cornerRadius = strokeThickness * 0.5f;
|
||||
self.strokeView.backgroundColor = [self strokeColorWithViewItem:viewItem];
|
||||
|
||||
self.subtitleLabel.hidden = self.subtitleLabel.text.length < 1;
|
||||
|
||||
[NSLayoutConstraint deactivateConstraints:self.layoutConstraints];
|
||||
self.layoutConstraints = @[
|
||||
[self.strokeView autoSetDimension:ALDimensionHeight toSize:strokeThickness],
|
||||
|
||||
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTop],
|
||||
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:conversationStyle.fullWidthGutterLeading],
|
||||
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:conversationStyle.fullWidthGutterTrailing],
|
||||
];
|
||||
}
|
||||
|
||||
- (CGFloat)strokeThicknessWithViewItem:(ConversationViewItem *)viewItem
|
||||
{
|
||||
OWSAssert(viewItem);
|
||||
|
||||
if (viewItem.unreadIndicator) {
|
||||
return 4.f;
|
||||
} else {
|
||||
return 1.f;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIColor *)strokeColorWithViewItem:(ConversationViewItem *)viewItem
|
||||
{
|
||||
OWSAssert(viewItem);
|
||||
|
||||
if (viewItem.unreadIndicator) {
|
||||
return UIColor.ows_light60Color;
|
||||
} else {
|
||||
return UIColor.ows_light45Color;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configureLabelsWithViewItem:(ConversationViewItem *)viewItem
|
||||
{
|
||||
OWSAssert(viewItem);
|
||||
|
||||
NSDate *date = viewItem.interaction.dateForSorting;
|
||||
NSString *dateString = [DateUtil formatDateForConversationDateBreaks:date].localizedUppercaseString;
|
||||
|
||||
// Update cell to reflect changes in dynamic text.
|
||||
if (viewItem.unreadIndicator) {
|
||||
self.titleLabel.font = UIFont.ows_dynamicTypeCaption1Font.ows_mediumWeight;
|
||||
|
||||
NSString *unreadTitle = NSLocalizedString(
|
||||
@"MESSAGES_VIEW_UNREAD_INDICATOR", @"Indicator that separates read from unread messages.");
|
||||
self.titleLabel.text = [[dateString rtlSafeAppend:@" • "] rtlSafeAppend:unreadTitle].localizedUppercaseString;
|
||||
|
||||
if (!viewItem.unreadIndicator.hasMoreUnseenMessages) {
|
||||
self.subtitleLabel.text = nil;
|
||||
} else {
|
||||
self.subtitleLabel.text = (viewItem.unreadIndicator.missingUnseenSafetyNumberChangeCount > 0
|
||||
? NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES",
|
||||
@"Messages that indicates that there are more unseen messages.")
|
||||
: NSLocalizedString(
|
||||
@"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_AND_SAFETY_NUMBER_CHANGES",
|
||||
@"Messages that indicates that there are more unseen messages including safety number "
|
||||
@"changes."));
|
||||
}
|
||||
} else {
|
||||
self.titleLabel.font = UIFont.ows_dynamicTypeCaption1Font;
|
||||
self.titleLabel.text = dateString;
|
||||
self.subtitleLabel.text = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)measureWithConversationViewItem:(ConversationViewItem *)viewItem
|
||||
conversationStyle:(ConversationStyle *)conversationStyle
|
||||
{
|
||||
OWSAssert(viewItem);
|
||||
OWSAssert(conversationStyle);
|
||||
|
||||
[self configureLabelsWithViewItem:viewItem];
|
||||
|
||||
CGSize result = CGSizeMake(conversationStyle.viewWidth, 0);
|
||||
|
||||
CGFloat strokeThickness = [self strokeThicknessWithViewItem:viewItem];
|
||||
result.height += strokeThickness;
|
||||
|
||||
CGFloat maxTextWidth = conversationStyle.fullWidthContentWidth;
|
||||
CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)];
|
||||
result.height += titleSize.height + self.stackView.spacing;
|
||||
|
||||
if (self.subtitleLabel.text.length > 0) {
|
||||
CGSize subtitleSize = [self.subtitleLabel sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)];
|
||||
result.height += subtitleSize.height + self.stackView.spacing;
|
||||
}
|
||||
result.height += OWSMessageHeaderViewDateHeaderVMargin;
|
||||
|
||||
return CGSizeCeil(result);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -1,17 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ConversationViewCell.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TSUnreadIndicatorInteraction;
|
||||
|
||||
@interface OWSUnreadIndicatorCell : ConversationViewCell
|
||||
|
||||
+ (NSString *)cellReuseIdentifier;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -1,169 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSUnreadIndicatorCell.h"
|
||||
#import "ConversationViewItem.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <SignalMessaging/TSUnreadIndicatorInteraction.h>
|
||||
#import <SignalMessaging/UIColor+OWS.h>
|
||||
#import <SignalMessaging/UIFont+OWS.h>
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSUnreadIndicatorCell ()
|
||||
|
||||
@property (nonatomic, nullable) TSUnreadIndicatorInteraction *interaction;
|
||||
|
||||
@property (nonatomic) UILabel *titleLabel;
|
||||
@property (nonatomic) UILabel *subtitleLabel;
|
||||
@property (nonatomic) UIView *strokeView;
|
||||
@property (nonatomic) NSArray<NSLayoutConstraint *> *layoutConstraints;
|
||||
@property (nonatomic) UIStackView *stackView;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSUnreadIndicatorCell
|
||||
|
||||
// `[UIView init]` invokes `[self initWithFrame:...]`.
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self commontInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)commontInit
|
||||
{
|
||||
OWSAssert(!self.titleLabel);
|
||||
|
||||
self.layoutMargins = UIEdgeInsetsZero;
|
||||
self.contentView.layoutMargins = UIEdgeInsetsZero;
|
||||
|
||||
self.strokeView = [UIView new];
|
||||
self.strokeView.backgroundColor = [UIColor ows_darkSkyBlueColor];
|
||||
[self.strokeView autoSetDimension:ALDimensionHeight toSize:self.strokeThickness];
|
||||
self.strokeView.layer.cornerRadius = self.strokeThickness * 0.5f;
|
||||
[self.strokeView setContentHuggingHigh];
|
||||
|
||||
self.titleLabel = [UILabel new];
|
||||
self.titleLabel.textColor = [UIColor ows_light90Color];
|
||||
self.titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
|
||||
self.subtitleLabel = [UILabel new];
|
||||
self.subtitleLabel.textColor = [UIColor ows_light90Color];
|
||||
// The subtitle may wrap to a second line.
|
||||
self.subtitleLabel.numberOfLines = 0;
|
||||
self.subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
self.subtitleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
|
||||
self.stackView = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
self.strokeView,
|
||||
self.titleLabel,
|
||||
self.subtitleLabel,
|
||||
]];
|
||||
self.stackView.axis = NSTextLayoutOrientationVertical;
|
||||
self.stackView.spacing = 2;
|
||||
[self.contentView addSubview:self.stackView];
|
||||
|
||||
[self configureFonts];
|
||||
}
|
||||
|
||||
- (void)configureFonts
|
||||
{
|
||||
// Update cell to reflect changes in dynamic text.
|
||||
self.titleLabel.font = UIFont.ows_dynamicTypeCaption1Font.ows_mediumWeight;
|
||||
self.subtitleLabel.font = UIFont.ows_dynamicTypeCaption1Font;
|
||||
}
|
||||
|
||||
+ (NSString *)cellReuseIdentifier
|
||||
{
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
{
|
||||
OWSAssert(self.conversationStyle);
|
||||
OWSAssert(self.viewItem);
|
||||
OWSAssert([self.viewItem.interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]);
|
||||
|
||||
[self configureFonts];
|
||||
|
||||
TSUnreadIndicatorInteraction *interaction = (TSUnreadIndicatorInteraction *)self.viewItem.interaction;
|
||||
|
||||
self.titleLabel.text = [self titleForInteraction:interaction];
|
||||
self.subtitleLabel.text = [self subtitleForInteraction:interaction];
|
||||
|
||||
self.subtitleLabel.hidden = self.subtitleLabel.text.length < 1;
|
||||
|
||||
[NSLayoutConstraint deactivateConstraints:self.layoutConstraints];
|
||||
self.layoutConstraints = @[
|
||||
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTop],
|
||||
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom],
|
||||
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeLeading
|
||||
withInset:self.conversationStyle.fullWidthGutterLeading],
|
||||
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing
|
||||
withInset:self.conversationStyle.fullWidthGutterTrailing],
|
||||
];
|
||||
}
|
||||
|
||||
- (NSString *)titleForInteraction:(TSUnreadIndicatorInteraction *)interaction
|
||||
{
|
||||
return NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR", @"Indicator that separates read from unread messages.")
|
||||
.localizedUppercaseString;
|
||||
}
|
||||
|
||||
- (NSString *)subtitleForInteraction:(TSUnreadIndicatorInteraction *)interaction
|
||||
{
|
||||
if (!interaction.hasMoreUnseenMessages) {
|
||||
return nil;
|
||||
}
|
||||
return (interaction.missingUnseenSafetyNumberChangeCount > 0
|
||||
? NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES",
|
||||
@"Messages that indicates that there are more unseen messages.")
|
||||
: NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_AND_SAFETY_NUMBER_CHANGES",
|
||||
@"Messages that indicates that there are more unseen messages including safety number changes."));
|
||||
}
|
||||
|
||||
- (CGFloat)strokeThickness
|
||||
{
|
||||
return 4.f;
|
||||
}
|
||||
|
||||
- (CGSize)cellSizeWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
{
|
||||
OWSAssert(self.conversationStyle);
|
||||
OWSAssert(self.viewItem);
|
||||
OWSAssert([self.viewItem.interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]);
|
||||
|
||||
[self configureFonts];
|
||||
|
||||
CGSize result = CGSizeMake(
|
||||
self.conversationStyle.fullWidthContentWidth, self.strokeThickness + self.titleLabel.font.lineHeight);
|
||||
|
||||
TSUnreadIndicatorInteraction *interaction = (TSUnreadIndicatorInteraction *)self.viewItem.interaction;
|
||||
self.subtitleLabel.text = [self subtitleForInteraction:interaction];
|
||||
if (self.subtitleLabel.text.length > 0) {
|
||||
result.height += ceil(
|
||||
[self.subtitleLabel sizeThatFits:CGSizeMake(self.conversationStyle.fullWidthContentWidth, CGFLOAT_MAX)]
|
||||
.height);
|
||||
}
|
||||
|
||||
return CGSizeCeil(result);
|
||||
}
|
||||
|
||||
- (void)prepareForReuse
|
||||
{
|
||||
[super prepareForReuse];
|
||||
|
||||
self.interaction = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -27,7 +27,6 @@
|
||||
#import "OWSMath.h"
|
||||
#import "OWSMessageCell.h"
|
||||
#import "OWSSystemMessageCell.h"
|
||||
#import "OWSUnreadIndicatorCell.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import "SignalKeyingStorage.h"
|
||||
#import "TSAttachmentPointer.h"
|
||||
@ -53,8 +52,8 @@
|
||||
#import <SignalMessaging/OWSContactsManager.h>
|
||||
#import <SignalMessaging/OWSFormat.h>
|
||||
#import <SignalMessaging/OWSNavigationController.h>
|
||||
#import <SignalMessaging/OWSUnreadIndicator.h>
|
||||
#import <SignalMessaging/OWSUserProfile.h>
|
||||
#import <SignalMessaging/TSUnreadIndicatorInteraction.h>
|
||||
#import <SignalMessaging/ThreadUtil.h>
|
||||
#import <SignalMessaging/UIUtil.h>
|
||||
#import <SignalMessaging/UIViewController+OWS.h>
|
||||
@ -641,8 +640,6 @@ typedef enum : NSUInteger {
|
||||
{
|
||||
[self.collectionView registerClass:[OWSSystemMessageCell class]
|
||||
forCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier]];
|
||||
[self.collectionView registerClass:[OWSUnreadIndicatorCell class]
|
||||
forCellWithReuseIdentifier:[OWSUnreadIndicatorCell cellReuseIdentifier]];
|
||||
[self.collectionView registerClass:[OWSContactOffersCell class]
|
||||
forCellWithReuseIdentifier:[OWSContactOffersCell cellReuseIdentifier]];
|
||||
[self.collectionView registerClass:[OWSMessageCell class]
|
||||
@ -1641,7 +1638,7 @@ typedef enum : NSUInteger {
|
||||
|
||||
- (void)loadAnotherPageOfMessages
|
||||
{
|
||||
BOOL hasEarlierUnseenMessages = self.dynamicInteractions.hasMoreUnseenMessages;
|
||||
BOOL hasEarlierUnseenMessages = self.dynamicInteractions.unreadIndicator.hasMoreUnseenMessages;
|
||||
|
||||
[self loadNMoreMessages:kYapDatabasePageSize];
|
||||
|
||||
@ -1746,9 +1743,9 @@ typedef enum : NSUInteger {
|
||||
}
|
||||
}
|
||||
|
||||
if (self.dynamicInteractions.unreadIndicatorPosition) {
|
||||
if (self.dynamicInteractions.unreadIndicator) {
|
||||
NSUInteger unreadIndicatorPosition
|
||||
= (NSUInteger)[self.dynamicInteractions.unreadIndicatorPosition longValue];
|
||||
= (NSUInteger)self.dynamicInteractions.unreadIndicator.unreadIndicatorPosition;
|
||||
|
||||
// If there is an unread indicator, increase the initial load window
|
||||
// to include it.
|
||||
@ -2479,15 +2476,14 @@ typedef enum : NSUInteger {
|
||||
const int currentMaxRangeSize = (int)self.lastRangeLength;
|
||||
const int maxRangeSize = MAX(kConversationInitialMaxRangeSize, currentMaxRangeSize);
|
||||
|
||||
self.dynamicInteractions =
|
||||
[ThreadUtil ensureDynamicInteractionsForThread:self.thread
|
||||
contactsManager:self.contactsManager
|
||||
blockingManager:self.blockingManager
|
||||
dbConnection:self.editingDatabaseConnection
|
||||
hideUnreadMessagesIndicator:self.hasClearedUnreadMessagesIndicator
|
||||
firstUnseenInteractionTimestamp:self.dynamicInteractions.firstUnseenInteractionTimestamp
|
||||
focusMessageId:self.focusMessageIdOnOpen
|
||||
maxRangeSize:maxRangeSize];
|
||||
self.dynamicInteractions = [ThreadUtil ensureDynamicInteractionsForThread:self.thread
|
||||
contactsManager:self.contactsManager
|
||||
blockingManager:self.blockingManager
|
||||
dbConnection:self.editingDatabaseConnection
|
||||
hideUnreadMessagesIndicator:self.hasClearedUnreadMessagesIndicator
|
||||
lastUnreadIndicator:self.dynamicInteractions.unreadIndicator
|
||||
focusMessageId:self.focusMessageIdOnOpen
|
||||
maxRangeSize:maxRangeSize];
|
||||
}
|
||||
|
||||
- (void)clearUnreadMessagesIndicator
|
||||
@ -2504,7 +2500,7 @@ typedef enum : NSUInteger {
|
||||
// make sure we don't show it again.
|
||||
self.hasClearedUnreadMessagesIndicator = YES;
|
||||
|
||||
if (self.dynamicInteractions.unreadIndicatorPosition) {
|
||||
if (self.dynamicInteractions.unreadIndicator) {
|
||||
// If we've just cleared the "unread messages" indicator,
|
||||
// update the dynamic interactions.
|
||||
[self ensureDynamicInteractions];
|
||||
@ -3793,18 +3789,10 @@ typedef enum : NSUInteger {
|
||||
|
||||
- (void)cleanUpUnreadIndicatorIfNecessary
|
||||
{
|
||||
BOOL hasUnreadIndicator = self.dynamicInteractions.unreadIndicatorPosition != nil;
|
||||
BOOL hasUnreadIndicator = self.dynamicInteractions.unreadIndicator != nil;
|
||||
if (!hasUnreadIndicator) {
|
||||
return;
|
||||
}
|
||||
__block BOOL hasUnseenInteractions = NO;
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
hasUnseenInteractions =
|
||||
[[transaction ext:TSUnreadDatabaseViewExtensionName] numberOfItemsInGroup:self.thread.uniqueId] > 0;
|
||||
}];
|
||||
if (hasUnseenInteractions) {
|
||||
return;
|
||||
}
|
||||
// If the last unread message was deleted (manually or due to disappearing messages)
|
||||
// we may need to clean up an obsolete unread indicator.
|
||||
[self ensureDynamicInteractions];
|
||||
@ -3918,7 +3906,7 @@ typedef enum : NSUInteger {
|
||||
|
||||
__block NSString *draft;
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
draft = [_thread currentDraftWithTransaction:transaction];
|
||||
draft = [self.thread currentDraftWithTransaction:transaction];
|
||||
}];
|
||||
[self.inputToolbar setMessageText:draft animated:NO];
|
||||
}
|
||||
@ -4790,6 +4778,7 @@ typedef enum : NSUInteger {
|
||||
// Update the properties of the view items.
|
||||
//
|
||||
// NOTE: This logic uses shouldShowDate which is set in the previous pass.
|
||||
OWSUnreadIndicator *_Nullable unreadIndicator = self.dynamicInteractions.unreadIndicator;
|
||||
for (NSUInteger i = 0; i < viewItems.count; i++) {
|
||||
ConversationViewItem *viewItem = viewItems[i];
|
||||
ConversationViewItem *_Nullable previousViewItem = (i > 0 ? viewItems[i - 1] : nil);
|
||||
@ -4800,6 +4789,15 @@ typedef enum : NSUInteger {
|
||||
BOOL isLastInCluster = YES;
|
||||
NSAttributedString *_Nullable senderName = nil;
|
||||
|
||||
// Place the unread indicator onto the first appropriate view item,
|
||||
// if any.
|
||||
if (unreadIndicator && viewItem.interaction.timestampForSorting >= unreadIndicator.timestamp) {
|
||||
viewItem.unreadIndicator = unreadIndicator;
|
||||
unreadIndicator = nil;
|
||||
} else {
|
||||
viewItem.unreadIndicator = nil;
|
||||
}
|
||||
|
||||
OWSInteractionType interactionType = viewItem.interaction.interactionType;
|
||||
NSString *timestampText = [DateUtil formatTimestampShort:viewItem.interaction.timestamp];
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
||||
@class DisplayableText;
|
||||
@class OWSAudioMessageView;
|
||||
@class OWSQuotedReplyModel;
|
||||
@class OWSUnreadIndicator;
|
||||
@class TSAttachmentPointer;
|
||||
@class TSAttachmentStream;
|
||||
@class TSInteraction;
|
||||
@ -53,6 +54,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
||||
@property (nonatomic, readonly) BOOL isQuotedReply;
|
||||
@property (nonatomic, readonly) BOOL hasQuotedAttachment;
|
||||
@property (nonatomic, readonly) BOOL hasQuotedText;
|
||||
@property (nonatomic, readonly) BOOL hasCellHeader;
|
||||
|
||||
@property (nonatomic, readonly) BOOL isExpiringMessage;
|
||||
|
||||
@ -63,6 +65,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
||||
@property (nonatomic) BOOL isFirstInCluster;
|
||||
@property (nonatomic) BOOL isLastInCluster;
|
||||
|
||||
@property (nonatomic, nullable) OWSUnreadIndicator *unreadIndicator;
|
||||
|
||||
@property (nonatomic, readonly) ConversationStyle *conversationStyle;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@ -6,11 +6,12 @@
|
||||
#import "OWSAudioMessageView.h"
|
||||
#import "OWSContactOffersCell.h"
|
||||
#import "OWSMessageCell.h"
|
||||
#import "OWSMessageHeaderView.h"
|
||||
#import "OWSSystemMessageCell.h"
|
||||
#import "OWSUnreadIndicatorCell.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import <SignalMessaging/NSString+OWS.h>
|
||||
#import <SignalMessaging/OWSUnreadIndicator.h>
|
||||
#import <SignalServiceKit/OWSContact.h>
|
||||
#import <SignalServiceKit/TSInteraction.h>
|
||||
|
||||
@ -149,6 +150,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
return message.isExpiringMessage;
|
||||
}
|
||||
|
||||
- (BOOL)hasCellHeader
|
||||
{
|
||||
return self.shouldShowDate || self.unreadIndicator;
|
||||
}
|
||||
|
||||
- (void)setShouldShowDate:(BOOL)shouldShowDate
|
||||
{
|
||||
if (_shouldShowDate == shouldShowDate) {
|
||||
@ -193,6 +199,17 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
[self clearCachedLayoutState];
|
||||
}
|
||||
|
||||
- (void)setUnreadIndicator:(nullable OWSUnreadIndicator *)unreadIndicator
|
||||
{
|
||||
if ([NSObject isNullableObject:_unreadIndicator equalTo:unreadIndicator]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_unreadIndicator = unreadIndicator;
|
||||
|
||||
[self clearCachedLayoutState];
|
||||
}
|
||||
|
||||
- (void)clearCachedLayoutState
|
||||
{
|
||||
self.cachedCellSize = nil;
|
||||
@ -245,8 +262,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
measurementCell = [OWSSystemMessageCell new];
|
||||
break;
|
||||
case OWSInteractionType_UnreadIndicator:
|
||||
measurementCell = [OWSUnreadIndicatorCell new];
|
||||
break;
|
||||
OWSFail(@"%@ unexpected unread indicator.", self.logTag);
|
||||
return nil;
|
||||
case OWSInteractionType_Offer:
|
||||
measurementCell = [OWSContactOffersCell new];
|
||||
break;
|
||||
@ -268,8 +285,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
return 20.f;
|
||||
}
|
||||
|
||||
if (self.shouldShowDate) {
|
||||
return OWSMessageCellDateHeaderVMargin;
|
||||
if (self.hasCellHeader) {
|
||||
return OWSMessageHeaderViewDateHeaderVMargin;
|
||||
}
|
||||
|
||||
// "Bubble Collapse". Adjacent messages with the same author should be close together.
|
||||
@ -310,8 +327,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier]
|
||||
forIndexPath:indexPath];
|
||||
case OWSInteractionType_UnreadIndicator:
|
||||
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSUnreadIndicatorCell cellReuseIdentifier]
|
||||
forIndexPath:indexPath];
|
||||
OWSFail(@"%@ unexpected unread indicator.", self.logTag);
|
||||
return nil;
|
||||
case OWSInteractionType_Offer:
|
||||
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSContactOffersCell cellReuseIdentifier]
|
||||
forIndexPath:indexPath];
|
||||
|
||||
@ -6,22 +6,11 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// This class is vestigial.
|
||||
@interface TSUnreadIndicatorInteraction : TSInteraction
|
||||
|
||||
@property (atomic, readonly) BOOL hasMoreUnseenMessages;
|
||||
|
||||
@property (atomic, readonly) NSUInteger missingUnseenSafetyNumberChangeCount;
|
||||
|
||||
- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)initUnreadIndicatorWithTimestamp:(uint64_t)timestamp
|
||||
thread:(TSThread *)thread
|
||||
hasMoreUnseenMessages:(BOOL)hasMoreUnseenMessages
|
||||
missingUnseenSafetyNumberChangeCount:(NSUInteger)missingUnseenSafetyNumberChangeCount
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -6,14 +6,6 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TSUnreadIndicatorInteraction ()
|
||||
|
||||
@property (atomic) BOOL hasMoreUnseenMessages;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation TSUnreadIndicatorInteraction
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
@ -21,23 +13,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return [super initWithCoder:coder];
|
||||
}
|
||||
|
||||
- (instancetype)initUnreadIndicatorWithTimestamp:(uint64_t)timestamp
|
||||
thread:(TSThread *)thread
|
||||
hasMoreUnseenMessages:(BOOL)hasMoreUnseenMessages
|
||||
missingUnseenSafetyNumberChangeCount:(NSUInteger)missingUnseenSafetyNumberChangeCount
|
||||
{
|
||||
self = [super initInteractionWithTimestamp:timestamp inThread:thread];
|
||||
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_hasMoreUnseenMessages = hasMoreUnseenMessages;
|
||||
_missingUnseenSafetyNumberChangeCount = missingUnseenSafetyNumberChangeCount;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)shouldUseReceiptDateForSorting
|
||||
{
|
||||
// Use the timestamp, not the "received at" timestamp to sort,
|
||||
|
||||
@ -40,7 +40,6 @@ FOUNDATION_EXPORT const unsigned char SignalMessagingVersionString[];
|
||||
#import <SignalMessaging/ScreenLockViewController.h>
|
||||
#import <SignalMessaging/SharingThreadPickerViewController.h>
|
||||
#import <SignalMessaging/SignalKeyingStorage.h>
|
||||
#import <SignalMessaging/TSUnreadIndicatorInteraction.h>
|
||||
#import <SignalMessaging/ThreadUtil.h>
|
||||
#import <SignalMessaging/ThreadViewHelper.h>
|
||||
#import <SignalMessaging/UIColor+OWS.h>
|
||||
|
||||
35
SignalMessaging/utils/OWSUnreadIndicator.h
Normal file
35
SignalMessaging/utils/OWSUnreadIndicator.h
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSUnreadIndicator : NSObject
|
||||
|
||||
@property (nonatomic, readonly) uint64_t timestamp;
|
||||
|
||||
@property (nonatomic, readonly) BOOL hasMoreUnseenMessages;
|
||||
|
||||
@property (nonatomic, readonly) NSUInteger missingUnseenSafetyNumberChangeCount;
|
||||
|
||||
@property (nonatomic, readonly) uint64_t firstUnseenInteractionTimestamp;
|
||||
|
||||
// The index of the unseen indicator, counting from the _end_ of the conversation
|
||||
// history.
|
||||
//
|
||||
// This is used by MessageViewController to increase the
|
||||
// range size of the mappings (the load window of the conversation)
|
||||
// to include the unread indicator.
|
||||
@property (nonatomic, readonly) NSInteger unreadIndicatorPosition;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initUnreadIndicatorWithTimestamp:(uint64_t)timestamp
|
||||
hasMoreUnseenMessages:(BOOL)hasMoreUnseenMessages
|
||||
missingUnseenSafetyNumberChangeCount:(NSUInteger)missingUnseenSafetyNumberChangeCount
|
||||
unreadIndicatorPosition:(NSInteger)unreadIndicatorPosition
|
||||
firstUnseenInteractionTimestamp:(uint64_t)firstUnseenInteractionTimestamp NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
50
SignalMessaging/utils/OWSUnreadIndicator.m
Normal file
50
SignalMessaging/utils/OWSUnreadIndicator.m
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSUnreadIndicator.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation OWSUnreadIndicator
|
||||
|
||||
- (instancetype)initUnreadIndicatorWithTimestamp:(uint64_t)timestamp
|
||||
hasMoreUnseenMessages:(BOOL)hasMoreUnseenMessages
|
||||
missingUnseenSafetyNumberChangeCount:(NSUInteger)missingUnseenSafetyNumberChangeCount
|
||||
unreadIndicatorPosition:(NSInteger)unreadIndicatorPosition
|
||||
firstUnseenInteractionTimestamp:(uint64_t)firstUnseenInteractionTimestamp
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_timestamp = timestamp;
|
||||
_hasMoreUnseenMessages = hasMoreUnseenMessages;
|
||||
_missingUnseenSafetyNumberChangeCount = missingUnseenSafetyNumberChangeCount;
|
||||
_unreadIndicatorPosition = unreadIndicatorPosition;
|
||||
_firstUnseenInteractionTimestamp = firstUnseenInteractionTimestamp;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (![object isKindOfClass:[OWSUnreadIndicator class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
OWSUnreadIndicator *other = object;
|
||||
return (self.timestamp == other.timestamp && self.hasMoreUnseenMessages == other.hasMoreUnseenMessages
|
||||
&& self.missingUnseenSafetyNumberChangeCount == other.missingUnseenSafetyNumberChangeCount
|
||||
&& self.unreadIndicatorPosition == other.unreadIndicatorPosition);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -7,6 +7,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@class OWSBlockingManager;
|
||||
@class OWSContactsManager;
|
||||
@class OWSMessageSender;
|
||||
@class OWSUnreadIndicator;
|
||||
@class SignalAttachment;
|
||||
@class TSContactThread;
|
||||
@class TSInteraction;
|
||||
@ -16,15 +17,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ThreadDynamicInteractions : NSObject
|
||||
|
||||
// If there are unseen messages in the thread, this is the index
|
||||
// of the unseen indicator, counting from the _end_ of the conversation
|
||||
// history.
|
||||
//
|
||||
// This is used by MessageViewController to increase the
|
||||
// range size of the mappings (the load window of the conversation)
|
||||
// to include the unread indicator.
|
||||
@property (nonatomic, nullable, readonly) NSNumber *unreadIndicatorPosition;
|
||||
|
||||
// Represents the "reverse index" of the focus message, if any.
|
||||
// The "reverse index" is the distance of this interaction from
|
||||
// the last interaction in the thread. Therefore the last interaction
|
||||
@ -34,18 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// determine the initial load window size.
|
||||
@property (nonatomic, nullable, readonly) NSNumber *focusMessagePosition;
|
||||
|
||||
// If there are unseen messages in the thread, this is the timestamp
|
||||
// of the oldest unseen message.
|
||||
//
|
||||
// Once we enter messages view, we mark all messages read, so we need
|
||||
// a snapshot of what the first unread message was when we entered the
|
||||
// view so that we can call ensureDynamicInteractionsForThread:...
|
||||
// repeatedly. The unread indicator should continue to show up until
|
||||
// it has been cleared, at which point hideUnreadMessagesIndicator is
|
||||
// YES in ensureDynamicInteractionsForThread:...
|
||||
@property (nonatomic, nullable, readonly) NSNumber *firstUnseenInteractionTimestamp;
|
||||
|
||||
@property (nonatomic, readonly) BOOL hasMoreUnseenMessages;
|
||||
@property (nonatomic, nullable, readonly) OWSUnreadIndicator *unreadIndicator;
|
||||
|
||||
- (void)clearUnreadIndicatorState;
|
||||
|
||||
@ -113,7 +94,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
blockingManager:(OWSBlockingManager *)blockingManager
|
||||
dbConnection:(YapDatabaseConnection *)dbConnection
|
||||
hideUnreadMessagesIndicator:(BOOL)hideUnreadMessagesIndicator
|
||||
firstUnseenInteractionTimestamp:(nullable NSNumber *)firstUnseenInteractionTimestamp
|
||||
lastUnreadIndicator:(nullable OWSUnreadIndicator *)lastUnreadIndicator
|
||||
focusMessageId:(nullable NSString *)focusMessageId
|
||||
maxRangeSize:(int)maxRangeSize;
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#import "OWSContactOffersInteraction.h"
|
||||
#import "OWSContactsManager.h"
|
||||
#import "OWSQuotedReplyModel.h"
|
||||
#import "OWSUnreadIndicator.h"
|
||||
#import "TSUnreadIndicatorInteraction.h"
|
||||
#import <SignalMessaging/OWSProfileManager.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
@ -29,13 +30,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ThreadDynamicInteractions ()
|
||||
|
||||
@property (nonatomic, nullable) NSNumber *unreadIndicatorPosition;
|
||||
|
||||
@property (nonatomic, nullable) NSNumber *focusMessagePosition;
|
||||
|
||||
@property (nonatomic, nullable) NSNumber *firstUnseenInteractionTimestamp;
|
||||
|
||||
@property (nonatomic) BOOL hasMoreUnseenMessages;
|
||||
@property (nonatomic, nullable) OWSUnreadIndicator *unreadIndicator;
|
||||
|
||||
@end
|
||||
|
||||
@ -45,9 +42,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (void)clearUnreadIndicatorState
|
||||
{
|
||||
self.unreadIndicatorPosition = nil;
|
||||
self.firstUnseenInteractionTimestamp = nil;
|
||||
self.hasMoreUnseenMessages = NO;
|
||||
self.unreadIndicator = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -221,8 +216,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
blockingManager:(OWSBlockingManager *)blockingManager
|
||||
dbConnection:(YapDatabaseConnection *)dbConnection
|
||||
hideUnreadMessagesIndicator:(BOOL)hideUnreadMessagesIndicator
|
||||
firstUnseenInteractionTimestamp:
|
||||
(nullable NSNumber *)firstUnseenInteractionTimestampParameter
|
||||
lastUnreadIndicator:(nullable OWSUnreadIndicator *)lastUnreadIndicator
|
||||
focusMessageId:(nullable NSString *)focusMessageId
|
||||
maxRangeSize:(int)maxRangeSize
|
||||
{
|
||||
@ -256,7 +250,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// Find any "dynamic" interactions and safety number changes.
|
||||
//
|
||||
// We use different views for performance reasons.
|
||||
__block TSUnreadIndicatorInteraction *existingUnreadIndicator = nil;
|
||||
__block OWSContactOffersInteraction *existingContactOffers = nil;
|
||||
NSMutableArray<TSInvalidIdentityKeyErrorMessage *> *blockingSafetyNumberChanges = [NSMutableArray new];
|
||||
NSMutableArray<TSInteraction *> *nonBlockingSafetyNumberChanges = [NSMutableArray new];
|
||||
@ -280,13 +273,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// the OWSContactOffersInteraction.
|
||||
[interactionsToDelete addObject:object];
|
||||
} else if ([object isKindOfClass:[TSUnreadIndicatorInteraction class]]) {
|
||||
OWSAssert(!existingUnreadIndicator);
|
||||
if (existingUnreadIndicator) {
|
||||
// There should never be more than one unread indicator in
|
||||
// a given thread, but if there is, discard all but one.
|
||||
[interactionsToDelete addObject:existingUnreadIndicator];
|
||||
}
|
||||
existingUnreadIndicator = (TSUnreadIndicatorInteraction *)object;
|
||||
// Remove obsolete unread indicator interactions;
|
||||
[interactionsToDelete addObject:object];
|
||||
} else if ([object isKindOfClass:[OWSContactOffersInteraction class]]) {
|
||||
OWSAssert(!existingContactOffers);
|
||||
if (existingContactOffers) {
|
||||
@ -318,13 +306,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// have been marked as read.
|
||||
//
|
||||
// IFF this variable is non-null, there are unseen messages in the thread.
|
||||
if (firstUnseenInteractionTimestampParameter) {
|
||||
result.firstUnseenInteractionTimestamp = firstUnseenInteractionTimestampParameter;
|
||||
NSNumber *_Nullable firstUnseenInteractionTimestamp = nil;
|
||||
if (lastUnreadIndicator) {
|
||||
firstUnseenInteractionTimestamp = @(lastUnreadIndicator.firstUnseenInteractionTimestamp);
|
||||
} else {
|
||||
TSInteraction *_Nullable firstUnseenInteraction =
|
||||
[[TSDatabaseView unseenDatabaseViewExtension:transaction] firstObjectInGroup:thread.uniqueId];
|
||||
if (firstUnseenInteraction) {
|
||||
result.firstUnseenInteractionTimestamp = @(firstUnseenInteraction.timestampForSorting);
|
||||
firstUnseenInteractionTimestamp = @(firstUnseenInteraction.timestampForSorting);
|
||||
}
|
||||
}
|
||||
|
||||
@ -481,14 +470,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
|
||||
[self ensureUnreadIndicator:result
|
||||
thread:thread
|
||||
transaction:transaction
|
||||
shouldHaveContactOffers:shouldHaveContactOffers
|
||||
maxRangeSize:maxRangeSize
|
||||
blockingSafetyNumberChanges:blockingSafetyNumberChanges
|
||||
nonBlockingSafetyNumberChanges:nonBlockingSafetyNumberChanges
|
||||
existingUnreadIndicator:existingUnreadIndicator
|
||||
hideUnreadMessagesIndicator:hideUnreadMessagesIndicator];
|
||||
thread:thread
|
||||
transaction:transaction
|
||||
shouldHaveContactOffers:shouldHaveContactOffers
|
||||
maxRangeSize:maxRangeSize
|
||||
blockingSafetyNumberChanges:blockingSafetyNumberChanges
|
||||
nonBlockingSafetyNumberChanges:nonBlockingSafetyNumberChanges
|
||||
hideUnreadMessagesIndicator:hideUnreadMessagesIndicator
|
||||
firstUnseenInteractionTimestamp:firstUnseenInteractionTimestamp];
|
||||
|
||||
// Determine the position of the focus message _after_ performing any mutations
|
||||
// around dynamic interactions.
|
||||
@ -502,14 +491,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
|
||||
+ (void)ensureUnreadIndicator:(ThreadDynamicInteractions *)dynamicInteractions
|
||||
thread:(TSThread *)thread
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
shouldHaveContactOffers:(BOOL)shouldHaveContactOffers
|
||||
maxRangeSize:(int)maxRangeSize
|
||||
blockingSafetyNumberChanges:(NSArray<TSInvalidIdentityKeyErrorMessage *> *)blockingSafetyNumberChanges
|
||||
nonBlockingSafetyNumberChanges:(NSArray<TSInteraction *> *)nonBlockingSafetyNumberChanges
|
||||
existingUnreadIndicator:(nullable TSUnreadIndicatorInteraction *)existingUnreadIndicator
|
||||
hideUnreadMessagesIndicator:(BOOL)hideUnreadMessagesIndicator
|
||||
thread:(TSThread *)thread
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
shouldHaveContactOffers:(BOOL)shouldHaveContactOffers
|
||||
maxRangeSize:(int)maxRangeSize
|
||||
blockingSafetyNumberChanges:(NSArray<TSInvalidIdentityKeyErrorMessage *> *)blockingSafetyNumberChanges
|
||||
nonBlockingSafetyNumberChanges:(NSArray<TSInteraction *> *)nonBlockingSafetyNumberChanges
|
||||
hideUnreadMessagesIndicator:(BOOL)hideUnreadMessagesIndicator
|
||||
firstUnseenInteractionTimestamp:(nullable NSNumber *)firstUnseenInteractionTimestamp
|
||||
{
|
||||
OWSAssert(dynamicInteractions);
|
||||
OWSAssert(thread);
|
||||
@ -517,6 +506,17 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
OWSAssert(blockingSafetyNumberChanges);
|
||||
OWSAssert(nonBlockingSafetyNumberChanges);
|
||||
|
||||
if (hideUnreadMessagesIndicator) {
|
||||
return;
|
||||
}
|
||||
if (!firstUnseenInteractionTimestamp) {
|
||||
// If there are no unseen interactions, don't show an unread indicator.
|
||||
return;
|
||||
}
|
||||
|
||||
YapDatabaseViewTransaction *threadMessagesTransaction = [transaction ext:TSMessageDatabaseViewExtensionName];
|
||||
OWSAssert([threadMessagesTransaction isKindOfClass:[YapDatabaseViewTransaction class]]);
|
||||
|
||||
// Determine unread indicator position, if necessary.
|
||||
//
|
||||
// Enumerate in reverse to count the number of messages
|
||||
@ -527,137 +527,101 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// the unread indicator.
|
||||
__block long visibleUnseenMessageCount = 0;
|
||||
__block TSInteraction *interactionAfterUnreadIndicator = nil;
|
||||
__block BOOL hasMoreUnseenMessages = NO;
|
||||
[threadMessagesTransaction
|
||||
enumerateRowsInGroup:thread.uniqueId
|
||||
withOptions:NSEnumerationReverse
|
||||
usingBlock:^(
|
||||
NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) {
|
||||
if (![object isKindOfClass:[TSInteraction class]]) {
|
||||
OWSFail(@"Expected a TSInteraction: %@", [object class]);
|
||||
return;
|
||||
}
|
||||
|
||||
TSInteraction *interaction = (TSInteraction *)object;
|
||||
|
||||
if (interaction.isDynamicInteraction) {
|
||||
// Ignore dynamic interactions, if any.
|
||||
return;
|
||||
}
|
||||
|
||||
if (interaction.timestampForSorting < firstUnseenInteractionTimestamp.unsignedLongLongValue) {
|
||||
// By default we want the unread indicator to appear just before
|
||||
// the first unread message.
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
visibleUnseenMessageCount++;
|
||||
|
||||
interactionAfterUnreadIndicator = interaction;
|
||||
|
||||
if (visibleUnseenMessageCount + 1 >= maxRangeSize) {
|
||||
// If there are more unseen messages than can be displayed in the
|
||||
// messages view, show the unread indicator at the top of the
|
||||
// displayed messages.
|
||||
*stop = YES;
|
||||
hasMoreUnseenMessages = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
if (!interactionAfterUnreadIndicator) {
|
||||
// If we can't find an interaction after the unread indicator,
|
||||
// remove it. All unread messages may have been deleted or
|
||||
// expired.
|
||||
return;
|
||||
}
|
||||
OWSAssert(visibleUnseenMessageCount > 0);
|
||||
|
||||
NSUInteger missingUnseenSafetyNumberChangeCount = 0;
|
||||
if (dynamicInteractions.firstUnseenInteractionTimestamp != nil) {
|
||||
[[transaction ext:TSMessageDatabaseViewExtensionName]
|
||||
enumerateRowsInGroup:thread.uniqueId
|
||||
withOptions:NSEnumerationReverse
|
||||
usingBlock:^(
|
||||
NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) {
|
||||
if (![object isKindOfClass:[TSInteraction class]]) {
|
||||
OWSFail(@"Expected a TSInteraction: %@", [object class]);
|
||||
return;
|
||||
}
|
||||
|
||||
TSInteraction *interaction = (TSInteraction *)object;
|
||||
|
||||
if (interaction.isDynamicInteraction) {
|
||||
// Ignore dynamic interactions, if any.
|
||||
return;
|
||||
}
|
||||
|
||||
if (interaction.timestampForSorting
|
||||
< dynamicInteractions.firstUnseenInteractionTimestamp.unsignedLongLongValue) {
|
||||
// By default we want the unread indicator to appear just before
|
||||
// the first unread message.
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
visibleUnseenMessageCount++;
|
||||
|
||||
interactionAfterUnreadIndicator = interaction;
|
||||
|
||||
if (visibleUnseenMessageCount + 1 >= maxRangeSize) {
|
||||
// If there are more unseen messages than can be displayed in the
|
||||
// messages view, show the unread indicator at the top of the
|
||||
// displayed messages.
|
||||
*stop = YES;
|
||||
dynamicInteractions.hasMoreUnseenMessages = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
if (!interactionAfterUnreadIndicator) {
|
||||
// If we can't find an interaction after the unread indicator,
|
||||
// remove it. All unread messages may have been deleted or
|
||||
// expired.
|
||||
dynamicInteractions.firstUnseenInteractionTimestamp = nil;
|
||||
} else if (dynamicInteractions.hasMoreUnseenMessages) {
|
||||
NSMutableSet<NSData *> *missingUnseenSafetyNumberChanges = [NSMutableSet set];
|
||||
for (TSInvalidIdentityKeyErrorMessage *safetyNumberChange in blockingSafetyNumberChanges) {
|
||||
BOOL isUnseen = safetyNumberChange.timestampForSorting
|
||||
>= dynamicInteractions.firstUnseenInteractionTimestamp.unsignedLongLongValue;
|
||||
if (!isUnseen) {
|
||||
continue;
|
||||
}
|
||||
BOOL isMissing
|
||||
= safetyNumberChange.timestampForSorting < interactionAfterUnreadIndicator.timestampForSorting;
|
||||
if (!isMissing) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSData *_Nullable newIdentityKey = safetyNumberChange.newIdentityKey;
|
||||
if (newIdentityKey == nil) {
|
||||
OWSFail(@"Safety number change was missing it's new identity key.");
|
||||
continue;
|
||||
}
|
||||
|
||||
[missingUnseenSafetyNumberChanges addObject:newIdentityKey];
|
||||
if (hasMoreUnseenMessages) {
|
||||
NSMutableSet<NSData *> *missingUnseenSafetyNumberChanges = [NSMutableSet set];
|
||||
for (TSInvalidIdentityKeyErrorMessage *safetyNumberChange in blockingSafetyNumberChanges) {
|
||||
BOOL isUnseen
|
||||
= safetyNumberChange.timestampForSorting >= firstUnseenInteractionTimestamp.unsignedLongLongValue;
|
||||
if (!isUnseen) {
|
||||
continue;
|
||||
}
|
||||
BOOL isMissing
|
||||
= safetyNumberChange.timestampForSorting < interactionAfterUnreadIndicator.timestampForSorting;
|
||||
if (!isMissing) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Count the de-duplicated "blocking" safety number changes and all
|
||||
// of the "non-blocking" safety number changes.
|
||||
missingUnseenSafetyNumberChangeCount
|
||||
= (missingUnseenSafetyNumberChanges.count + nonBlockingSafetyNumberChanges.count);
|
||||
}
|
||||
}
|
||||
if (dynamicInteractions.firstUnseenInteractionTimestamp) {
|
||||
// The unread indicator is _before_ the last visible unseen message.
|
||||
NSInteger unreadIndicatorPosition = visibleUnseenMessageCount + 1;
|
||||
if (shouldHaveContactOffers) {
|
||||
unreadIndicatorPosition++;
|
||||
}
|
||||
dynamicInteractions.unreadIndicatorPosition = @(unreadIndicatorPosition);
|
||||
}
|
||||
OWSAssert((dynamicInteractions.firstUnseenInteractionTimestamp != nil)
|
||||
== (dynamicInteractions.unreadIndicatorPosition != nil));
|
||||
|
||||
// Ensure unread indicator.
|
||||
//
|
||||
// We use this offset to control the ordering of the indicator.
|
||||
const int kUnreadIndicatorOffset = -1;
|
||||
NSUInteger threadMessageCount =
|
||||
[[transaction ext:TSMessageDatabaseViewExtensionName] numberOfItemsInGroup:thread.uniqueId];
|
||||
BOOL shouldHaveUnreadIndicator
|
||||
= (interactionAfterUnreadIndicator && !hideUnreadMessagesIndicator && threadMessageCount > 1);
|
||||
if (!shouldHaveUnreadIndicator) {
|
||||
if (existingUnreadIndicator) {
|
||||
DDLogInfo(@"%@ Removing obsolete TSUnreadIndicatorInteraction: %@",
|
||||
self.logTag,
|
||||
existingUnreadIndicator.uniqueId);
|
||||
[existingUnreadIndicator removeWithTransaction:transaction];
|
||||
}
|
||||
} else {
|
||||
// We want the unread indicator to appear just before the first unread incoming
|
||||
// message in the conversation timeline...
|
||||
//
|
||||
// ...unless we have a fixed timestamp for the unread indicator.
|
||||
uint64_t indicatorTimestamp
|
||||
= (uint64_t)((long long)interactionAfterUnreadIndicator.timestampForSorting + kUnreadIndicatorOffset);
|
||||
|
||||
if (indicatorTimestamp && existingUnreadIndicator.timestampForSorting == indicatorTimestamp) {
|
||||
// Keep the existing indicator; it is in the correct position.
|
||||
} else {
|
||||
if (existingUnreadIndicator) {
|
||||
DDLogInfo(@"%@ Removing TSUnreadIndicatorInteraction due to changed timestamp: %@",
|
||||
self.logTag,
|
||||
existingUnreadIndicator.uniqueId);
|
||||
[existingUnreadIndicator removeWithTransaction:transaction];
|
||||
NSData *_Nullable newIdentityKey = safetyNumberChange.newIdentityKey;
|
||||
if (newIdentityKey == nil) {
|
||||
OWSFail(@"Safety number change was missing it's new identity key.");
|
||||
continue;
|
||||
}
|
||||
|
||||
TSUnreadIndicatorInteraction *indicator = [[TSUnreadIndicatorInteraction alloc]
|
||||
initUnreadIndicatorWithTimestamp:indicatorTimestamp
|
||||
thread:thread
|
||||
hasMoreUnseenMessages:dynamicInteractions.hasMoreUnseenMessages
|
||||
missingUnseenSafetyNumberChangeCount:missingUnseenSafetyNumberChangeCount];
|
||||
[indicator saveWithTransaction:transaction];
|
||||
|
||||
DDLogInfo(@"%@ Creating TSUnreadIndicatorInteraction: %@ (%llu)",
|
||||
self.logTag,
|
||||
indicator.uniqueId,
|
||||
indicator.timestampForSorting);
|
||||
[missingUnseenSafetyNumberChanges addObject:newIdentityKey];
|
||||
}
|
||||
|
||||
// Count the de-duplicated "blocking" safety number changes and all
|
||||
// of the "non-blocking" safety number changes.
|
||||
missingUnseenSafetyNumberChangeCount
|
||||
= (missingUnseenSafetyNumberChanges.count + nonBlockingSafetyNumberChanges.count);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
NSInteger unreadIndicatorPosition = visibleUnseenMessageCount;
|
||||
// TODO:
|
||||
// if (dynamicInteractions.firstUnseenInteractionTimestamp) {
|
||||
// // The unread indicator is _before_ the last visible unseen message.
|
||||
// NSInteger unreadIndicatorPosition = visibleUnseenMessageCount + 1;
|
||||
// if (shouldHaveContactOffers) {
|
||||
// unreadIndicatorPosition++;
|
||||
// }
|
||||
// dynamicInteractions.unreadIndicatorPosition = @(unreadIndicatorPosition);
|
||||
// }
|
||||
|
||||
dynamicInteractions.unreadIndicator = [[OWSUnreadIndicator alloc]
|
||||
initUnreadIndicatorWithTimestamp:interactionAfterUnreadIndicator.timestampForSorting
|
||||
hasMoreUnseenMessages:hasMoreUnseenMessages
|
||||
missingUnseenSafetyNumberChangeCount:missingUnseenSafetyNumberChangeCount
|
||||
unreadIndicatorPosition:unreadIndicatorPosition
|
||||
firstUnseenInteractionTimestamp:firstUnseenInteractionTimestamp.unsignedLongLongValue];
|
||||
DDLogInfo(@"%@ Creating TSUnreadIndicator: %llu", self.logTag, dynamicInteractions.unreadIndicator.timestamp);
|
||||
}
|
||||
|
||||
+ (nullable NSNumber *)focusMessagePositionForThread:(TSThread *)thread
|
||||
|
||||
@ -15,6 +15,7 @@ typedef NS_ENUM(NSInteger, OWSInteractionType) {
|
||||
OWSInteractionType_Error,
|
||||
OWSInteractionType_Call,
|
||||
OWSInteractionType_Info,
|
||||
// TODO: Obsolete, consider replacing with OWSInteractionType_Unknown.
|
||||
OWSInteractionType_UnreadIndicator,
|
||||
OWSInteractionType_Offer,
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user