Add unread indicator.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-05-16 11:26:01 -04:00
parent 745172a6d1
commit ac458cc7ad
13 changed files with 392 additions and 1 deletions

View File

@ -72,6 +72,9 @@
34D5CCB11EAE7E7F005515DB /* SelectRecipientViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */; };
34DFCB851E8E04B500053165 /* AddToBlockListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DFCB841E8E04B500053165 /* AddToBlockListViewController.m */; };
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; };
34F3089C1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F3089B1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m */; };
34F3089F1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F3089E1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m */; };
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; };
34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */; };
450573FE1E78A06D00615BB4 /* OWS103EnableVideoCalling.m in Sources */ = {isa = PBXBuildFile; fileRef = 450573FD1E78A06D00615BB4 /* OWS103EnableVideoCalling.m */; };
4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4505C2BE1E648EA300CEBF41 /* ExperienceUpgrade.swift */; };
@ -472,6 +475,12 @@
34DFCB831E8E04B400053165 /* AddToBlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToBlockListViewController.h; sourceTree = "<group>"; };
34DFCB841E8E04B500053165 /* AddToBlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddToBlockListViewController.m; sourceTree = "<group>"; };
34E3E5671EC4B19400495BAC /* AudioProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioProgressView.swift; sourceTree = "<group>"; };
34F3089A1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSUnreadIndicatorInteraction.h; sourceTree = "<group>"; };
34F3089B1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSUnreadIndicatorInteraction.m; sourceTree = "<group>"; };
34F3089D1ECA580B00BB7697 /* OWSUnreadIndicatorCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUnreadIndicatorCell.h; sourceTree = "<group>"; };
34F3089E1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUnreadIndicatorCell.m; sourceTree = "<group>"; };
34F308A01ECB469700BB7697 /* OWSBezierPathView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBezierPathView.h; sourceTree = "<group>"; };
34F308A11ECB469700BB7697 /* OWSBezierPathView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBezierPathView.m; sourceTree = "<group>"; };
34FD936E1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSAnyTouchGestureRecognizer.h; path = views/OWSAnyTouchGestureRecognizer.h; sourceTree = "<group>"; };
34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSAnyTouchGestureRecognizer.m; path = views/OWSAnyTouchGestureRecognizer.m; sourceTree = "<group>"; };
450573FC1E78A06D00615BB4 /* OWS103EnableVideoCalling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWS103EnableVideoCalling.h; path = Migrations/OWS103EnableVideoCalling.h; sourceTree = "<group>"; };
@ -1307,6 +1316,8 @@
4531C9C21DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.h */,
4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */,
3453D8E91EC0D4ED003F9E6F /* OWSAlerts.swift */,
34F308A01ECB469700BB7697 /* OWSBezierPathView.h */,
34F308A11ECB469700BB7697 /* OWSBezierPathView.m */,
45C681B91D305C080050903A /* OWSCallCollectionViewCell.h */,
45C681BA1D305C080050903A /* OWSCallCollectionViewCell.m */,
45C681C01D305C9E0050903A /* OWSCallCollectionViewCell.xib */,
@ -1327,7 +1338,11 @@
45F2B1961D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib */,
34330AA11E79686200DF2FB9 /* OWSProgressView.h */,
34330AA21E79686200DF2FB9 /* OWSProgressView.m */,
34F3089D1ECA580B00BB7697 /* OWSUnreadIndicatorCell.h */,
34F3089E1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m */,
45A6DAD51EBBF85500893231 /* ReminderView.swift */,
34F3089A1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.h */,
34F3089B1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m */,
);
name = Views;
path = views;
@ -2045,6 +2060,7 @@
343D3D9B1E9283F100165CA4 /* BlockListUIUtils.m in Sources */,
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */,
76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */,
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */,
76EB058A18170B33006006FC /* Release.m in Sources */,
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */,
450873C71D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.m in Sources */,
@ -2074,6 +2090,7 @@
45666F561D9B2827008FE134 /* OWSScrubbingLogFormatter.m in Sources */,
45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */,
45C681C61D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.m in Sources */,
34F3089F1ECA580B00BB7697 /* OWSUnreadIndicatorCell.m in Sources */,
34B3F8861E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m in Sources */,
452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */,
452EA0971EA662330078744B /* AttachmentPointerAdapter.swift in Sources */,
@ -2158,6 +2175,7 @@
34B3F87D1E8DF1700035BE1A /* FullImageViewController.m in Sources */,
45666F7B1D9C0533008FE134 /* OWSDatabaseMigration.m in Sources */,
B90418E6183E9DD40038554A /* DateUtil.m in Sources */,
34F3089C1ECA4CDB00BB7697 /* TSUnreadIndicatorInteraction.m in Sources */,
45B201761DAECBFE00C461E0 /* HighlightableLabel.swift in Sources */,
459311FC1D75C948008DD4F0 /* OWSDeviceTableViewCell.m in Sources */,
);

View File

@ -15,6 +15,7 @@ typedef NS_ENUM(NSInteger, TSMessageAdapterType) {
TSErrorMessageAdapter,
TSMediaAttachmentAdapter,
TSGenericTextMessageAdapter, // Used when message direction is unknown (outgoing or incoming)
TSUnreadIndicatorAdapter,
};
@protocol OWSMessageData <JSQMessageData, OWSMessageEditing>

View File

@ -2,6 +2,7 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSMessageAdapter.h"
#import "AttachmentSharing.h"
#import "OWSCall.h"
#import "Signal-Swift.h"
@ -16,6 +17,7 @@
#import "TSIncomingMessage.h"
#import "TSInfoMessage.h"
#import "TSOutgoingMessage.h"
#import "TSUnreadIndicatorInteraction.h"
#import <MobileCoreServices/MobileCoreServices.h>
NS_ASSUME_NONNULL_BEGIN
@ -119,6 +121,8 @@ NS_ASSUME_NONNULL_BEGIN
adapter.senderDisplayName = NSLocalizedString(@"ME_STRING", @"");
adapter.messageType = TSOutgoingMessageAdapter;
}
} else {
OWSAssert(0);
}
if ([interaction isKindOfClass:[TSIncomingMessage class]] ||
@ -216,11 +220,15 @@ NS_ASSUME_NONNULL_BEGIN
displayString:@""];
return call;
}
} else {
} else if ([interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]) {
adapter.messageType = TSUnreadIndicatorAdapter;
} else if ([interaction isKindOfClass:[TSErrorMessage class]]) {
TSErrorMessage *errorMessage = (TSErrorMessage *)interaction;
adapter.errorMessageType = errorMessage.errorType;
adapter.messageBody = errorMessage.description;
adapter.messageType = TSErrorMessageAdapter;
} else {
OWSAssert(0);
}
if ([interaction isKindOfClass:[TSOutgoingMessage class]]) {

View File

@ -27,6 +27,7 @@
#import "OWSMessagesBubblesSizeCalculator.h"
#import "OWSOutgoingMessageCollectionViewCell.h"
#import "OWSUnknownContactBlockOfferMessage.h"
#import "OWSUnreadIndicatorCell.h"
#import "PropertyListPreferences.h"
#import "Signal-Swift.h"
#import "SignalKeyingStorage.h"
@ -707,6 +708,8 @@ typedef enum : NSUInteger {
_composeOnOpen = keyboardOnViewAppearing;
_callOnOpen = callOnViewAppearing;
[ThreadUtil createUnreadMessagesIndicatorIfNecessary:thread storageManager:self.storageManager];
[self markAllMessagesAsRead];
[self.uiDatabaseConnection beginLongLivedReadTransaction];
@ -813,6 +816,9 @@ typedef enum : NSUInteger {
[self.collectionView registerNib:[OWSCallCollectionViewCell nib]
forCellWithReuseIdentifier:[OWSCallCollectionViewCell cellReuseIdentifier]];
[self.collectionView registerClass:[OWSUnreadIndicatorCell class]
forCellWithReuseIdentifier:[OWSUnreadIndicatorCell cellReuseIdentifier]];
[self.collectionView registerNib:[OWSDisplayedMessageCollectionViewCell nib]
forCellWithReuseIdentifier:[OWSDisplayedMessageCollectionViewCell cellReuseIdentifier]];
@ -1685,6 +1691,9 @@ typedef enum : NSUInteger {
case TSOutgoingMessageAdapter: {
cell = [self loadOutgoingCellForMessage:message atIndexPath:indexPath];
} break;
case TSUnreadIndicatorAdapter: {
cell = [self loadUnreadIndicatorCell:indexPath];
} break;
default: {
DDLogWarn(@"using default cell constructor for message: %@", message);
cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView cellForItemAtIndexPath:indexPath];
@ -1712,6 +1721,7 @@ typedef enum : NSUInteger {
if (![cell isKindOfClass:[OWSIncomingMessageCollectionViewCell class]]) {
DDLogError(@"%@ Unexpected cell type: %@", self.tag, cell);
OWSAssert(0);
return cell;
}
@ -1732,6 +1742,7 @@ typedef enum : NSUInteger {
if (![cell isKindOfClass:[OWSOutgoingMessageCollectionViewCell class]]) {
DDLogError(@"%@ Unexpected cell type: %@", self.tag, cell);
OWSAssert(0);
return cell;
}
@ -1752,6 +1763,18 @@ typedef enum : NSUInteger {
return cell;
}
- (JSQMessagesCollectionViewCell *)loadUnreadIndicatorCell:(NSIndexPath *)indexPath
{
OWSAssert(indexPath);
OWSUnreadIndicatorCell *cell =
[self.collectionView dequeueReusableCellWithReuseIdentifier:[OWSUnreadIndicatorCell cellReuseIdentifier]
forIndexPath:indexPath];
[cell configure];
return cell;
}
- (OWSCallCollectionViewCell *)loadCallCellForCall:(OWSCall *)call atIndexPath:(NSIndexPath *)indexPath
{
OWSCallCollectionViewCell *callCell = [self.collectionView dequeueReusableCellWithReuseIdentifier:[OWSCallCollectionViewCell cellReuseIdentifier]
@ -2222,6 +2245,7 @@ typedef enum : NSUInteger {
[self handleErrorMessageTap:(TSErrorMessage *)interaction];
break;
case TSCallAdapter:
case TSUnreadIndicatorAdapter:
break;
default:
DDLogDebug(@"Unhandled bubble touch for interaction: %@.", interaction);

View File

@ -27,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN
contactsManager:(OWSContactsManager *)contactsManager
blockingManager:(OWSBlockingManager *)blockingManager;
+ (void)createUnreadMessagesIndicatorIfNecessary:(TSThread *)thread storageManager:(TSStorageManager *)storageManager;
@end
NS_ASSUME_NONNULL_END

View File

@ -5,6 +5,7 @@
#import "ThreadUtil.h"
#import "OWSContactsManager.h"
#import "Signal-Swift.h"
#import "TSUnreadIndicatorInteraction.h"
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
#import <SignalServiceKit/OWSBlockingManager.h>
#import <SignalServiceKit/OWSDisappearingMessagesConfiguration.h>
@ -178,6 +179,58 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
+ (void)createUnreadMessagesIndicatorIfNecessary:(TSThread *)thread storageManager:(TSStorageManager *)storageManager
{
OWSAssert(thread);
OWSAssert(storageManager);
[storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSMutableArray *indicators = [NSMutableArray new];
__block TSMessage *firstUnreadMessage = nil;
// TODO: Will this approach be prohibitively expensive?
[[transaction ext:TSMessageDatabaseViewExtensionName]
enumerateRowsInGroup:thread.uniqueId
usingBlock:^(
NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) {
if ([object isKindOfClass:[TSUnreadIndicatorInteraction class]]) {
[indicators addObject:object];
} else if ([object isKindOfClass:[TSIncomingMessage class]]) {
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)object;
if (!incomingMessage.wasRead) {
if (!firstUnreadMessage) {
firstUnreadMessage = incomingMessage;
} else {
OWSAssert([[firstUnreadMessage receiptDateForSorting]
compare:[incomingMessage receiptDateForSorting]]
== NSOrderedAscending);
}
}
}
}];
for (TSUnreadIndicatorInteraction *indicator in indicators) {
[indicator removeWithTransaction:transaction];
}
BOOL shouldHaveIndicator = firstUnreadMessage != nil;
if (!shouldHaveIndicator) {
return;
}
DDLogInfo(@"%@ Creating TSUnreadIndicatorInteraction", self.tag);
// We want the block offer to appear just before the first unread incoming
// message in the conversation timeline.
uint64_t indicatorTimestamp = firstUnreadMessage.timestamp - 1;
TSUnreadIndicatorInteraction *indicator =
[[TSUnreadIndicatorInteraction alloc] initWithTimestamp:indicatorTimestamp thread:thread];
[indicator saveWithTransaction:transaction];
}];
}
#pragma mark - Logging
+ (NSString *)tag

View File

@ -0,0 +1,22 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
typedef void (^ConfigureShapeLayerBlock)(CAShapeLayer *layer, CGRect bounds);
@interface OWSBezierPathView : UIView
// Configure the view with this method if it uses a single Bezier path.
- (void)setConfigureShapeLayerBlock:(ConfigureShapeLayerBlock)configureShapeLayerBlock;
// Configure the view with this method if it uses multiple Bezier paths.
//
// Paths will be rendered in back-to-front order.
- (void)setConfigureShapeLayerBlocks:(NSArray<ConfigureShapeLayerBlock> *)configureShapeLayerBlocks;
// This method forces the view to reconstruct its layer content. It shouldn't
// be necessary to call this unless the ConfigureShapeLayerBlocks depend on external
// state which has changed.
- (void)updateLayers;
@end

View File

@ -0,0 +1,110 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSBezierPathView.h"
@interface OWSBezierPathView ()
@property (nonatomic) NSArray<ConfigureShapeLayerBlock> *configureShapeLayerBlocks;
@end
@implementation OWSBezierPathView
- (id)init
{
self = [super init];
if (self) {
[self initCommon];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initCommon];
}
return self;
}
- (void)initCommon
{
self.opaque = NO;
self.userInteractionEnabled = NO;
self.backgroundColor = [UIColor clearColor];
}
- (void)setFrame:(CGRect)frame
{
BOOL didChangeSize = !CGSizeEqualToSize(frame.size, self.frame.size);
[super setFrame:frame];
if (didChangeSize) {
[self updateLayers];
}
}
- (void)setBounds:(CGRect)bounds
{
BOOL didChangeSize = !CGSizeEqualToSize(bounds.size, self.bounds.size);
[super setBounds:bounds];
if (didChangeSize) {
[self updateLayers];
}
}
- (void)setConfigureShapeLayerBlock:(ConfigureShapeLayerBlock)configureShapeLayerBlock
{
OWSAssert(configureShapeLayerBlock);
[self setConfigureShapeLayerBlocks:@[ configureShapeLayerBlock ]];
}
- (void)setConfigureShapeLayerBlocks:(NSArray<ConfigureShapeLayerBlock> *)configureShapeLayerBlocks
{
OWSAssert(configureShapeLayerBlocks.count > 0);
_configureShapeLayerBlocks = configureShapeLayerBlocks;
[self updateLayers];
}
- (void)updateLayers
{
if (self.bounds.size.width <= 0.f || self.bounds.size.height <= 0.f) {
return;
}
for (CALayer *layer in self.layer.sublayers) {
[layer removeFromSuperlayer];
}
for (ConfigureShapeLayerBlock configureShapeLayerBlock in self.configureShapeLayerBlocks) {
CAShapeLayer *shapeLayer = [CAShapeLayer new];
configureShapeLayerBlock(shapeLayer, self.bounds);
[self.layer addSublayer:shapeLayer];
}
[self setNeedsDisplay];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end

View File

@ -0,0 +1,12 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <JSQMessagesViewController/JSQMessagesCollectionViewCell.h>
#import <UIKit/UIKit.h>
@interface OWSUnreadIndicatorCell : JSQMessagesCollectionViewCell
- (void)configure;
@end

View File

@ -0,0 +1,77 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSUnreadIndicatorCell.h"
#import "OWSBezierPathView.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import <JSQMessagesViewController/UIView+JSQMessages.h>
@interface OWSUnreadIndicatorCell ()
@property (nonatomic) UILabel *label;
@property (nonatomic) OWSBezierPathView *leftPathView;
@property (nonatomic) OWSBezierPathView *rightPathView;
@end
#pragma mark -
@implementation OWSUnreadIndicatorCell
+ (NSString *)cellReuseIdentifier
{
return NSStringFromClass([self class]);
}
- (void)configure
{
self.backgroundColor = [UIColor whiteColor];
if (!self.label) {
self.label = [UILabel new];
self.label.text = NSLocalizedString(
@"MESSAGES_VIEW_UNREAD_INDICATOR", @"Indicator that separates read from unread messages.");
self.label.textColor = [UIColor ows_infoMessageBorderColor];
self.label.font = [UIFont ows_mediumFontWithSize:12.f];
[self.contentView addSubview:self.label];
CGFloat kLineThickness = 0.5f;
CGFloat kLineMargin = 5.f;
ConfigureShapeLayerBlock configureShapeLayerBlock = ^(CAShapeLayer *layer, CGRect bounds) {
OWSCAssert(layer);
CGRect pathBounds
= CGRectMake(0, (bounds.size.height - kLineThickness) * 0.5f, bounds.size.width, kLineThickness);
pathBounds = CGRectInset(pathBounds, kLineMargin, 0);
UIBezierPath *path = [UIBezierPath bezierPathWithRect:pathBounds];
layer.path = path.CGPath;
layer.fillColor = [UIColor ows_infoMessageBorderColor].CGColor;
};
self.leftPathView = [OWSBezierPathView new];
self.leftPathView.configureShapeLayerBlock = configureShapeLayerBlock;
[self.contentView addSubview:self.leftPathView];
self.rightPathView = [OWSBezierPathView new];
self.rightPathView.configureShapeLayerBlock = configureShapeLayerBlock;
[self.contentView addSubview:self.rightPathView];
}
}
- (void)layoutSubviews
{
CGSize labelSize = [self.label sizeThatFits:CGSizeZero];
self.label.frame = CGRectMake(round(self.bounds.origin.x + (self.bounds.size.width - labelSize.width) * 0.5f),
round(self.bounds.origin.y + (self.bounds.size.height - labelSize.height) * 0.5f),
labelSize.width,
labelSize.height);
self.leftPathView.frame = CGRectMake(0, 0, self.label.frame.origin.x, self.bounds.size.height);
self.rightPathView.frame = CGRectMake(self.label.frame.origin.x + self.label.frame.size.width,
0,
self.bounds.size.width - (self.label.frame.origin.x + self.label.frame.size.width),
self.bounds.size.height);
}
@end

View File

@ -0,0 +1,17 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSMessage.h"
NS_ASSUME_NONNULL_BEGIN
@interface TSUnreadIndicatorInteraction : TSMessage
- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimestamp:(uint64_t)timestamp thread:(TSThread *)thread NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,44 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSUnreadIndicatorInteraction.h"
NS_ASSUME_NONNULL_BEGIN
@implementation TSUnreadIndicatorInteraction
- (instancetype)initWithCoder:(NSCoder *)coder
{
return [super initWithCoder:coder];
}
- (instancetype)initWithTimestamp:(uint64_t)timestamp thread:(TSThread *)thread
{
self = [super initWithTimestamp:timestamp
inThread:thread
messageBody:nil
attachmentIds:@[]
expiresInSeconds:0
expireStartedAt:0];
if (!self) {
return self;
}
return self;
}
- (nullable NSDate *)receiptDateForSorting
{
// Always use date, since we're creating these interactions after the fact
// and back-dating them.
//
// By default [TSMessage receiptDateForSorting] will prefer to use receivedAtDate
// which is not back-dated.
return self.date;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -700,6 +700,9 @@
/* The subtitle for the messages view title indicates that the title can be tapped to access settings for this conversation. */
"MESSAGES_VIEW_TITLE_SUBTITLE" = "Tap here for settings";
/* Indicator that separates read from unread messages. */
"MESSAGES_VIEW_UNREAD_INDICATOR" = "Unread Messages";
/* {{number of minutes}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 minutes}}'. See other *_TIME_AMOUNT strings */
"MINUTES_TIME_AMOUNT" = "%u minutes";