Resize conversation view cells as necessary.

// FREEBIE
pull/1/head
Matthew Chen 8 years ago
parent f7bd813c9f
commit 227fd5280d

@ -220,7 +220,7 @@ NS_ASSUME_NONNULL_BEGIN
filename = [[self.attachmentStream filePath] lastPathComponent]; filename = [[self.attachmentStream filePath] lastPathComponent];
} }
NSString *topText = [[filename stringByDeletingPathExtension] NSString *topText = [[filename stringByDeletingPathExtension]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (topText.length < 1) { if (topText.length < 1) {
topText = [MIMETypeUtil fileExtensionForMIMEType:self.attachmentStream.contentType].uppercaseString; topText = [MIMETypeUtil fileExtensionForMIMEType:self.attachmentStream.contentType].uppercaseString;
} }

@ -56,11 +56,8 @@ NS_ASSUME_NONNULL_BEGIN
{ {
OWSAssert(!self.textLabel); OWSAssert(!self.textLabel);
// [self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.layoutMargins = UIEdgeInsetsZero; self.layoutMargins = UIEdgeInsetsZero;
self.contentView.backgroundColor = [UIColor whiteColor];
self.payloadView = [UIView containerView]; self.payloadView = [UIView containerView];
[self.contentView addSubview:self.payloadView]; [self.contentView addSubview:self.payloadView];

@ -121,7 +121,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSString *)trimmedText - (NSString *)trimmedText
{ {
return [self.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; return [self.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
} }
// TODO: // TODO:

@ -2769,7 +2769,10 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
return; return;
} }
[self reloadViewItems]; NSMutableSet<NSNumber *> *rowsThatChangedSize = [[self reloadViewItems] mutableCopy];
for (NSNumber *row in rowsThatChangedSize) {
DDLogError(@"might reload: %@", row);
}
BOOL wasAtBottom = [self isScrolledToBottom]; BOOL wasAtBottom = [self isScrolledToBottom];
// We want sending messages to feel snappy. So, if the only // We want sending messages to feel snappy. So, if the only
@ -2783,35 +2786,23 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
__block BOOL scrollToBottom = wasAtBottom; __block BOOL scrollToBottom = wasAtBottom;
[self.collectionView performBatchUpdates:^{ [self.collectionView performBatchUpdates:^{
// TODO: We need to reload neighbords of changed cells.
void (^reloadNeighbors)(NSIndexPath *) = ^(NSIndexPath *indexPath) {
// if (indexPath.row > 0) {
// [self.collectionView reloadItemsAtIndexPaths:@[ [NSIndexPath indexPathForRow:indexPath.row
// - 1
// inSection:0] ]];
// }
// if (indexPath.row + 1 < (NSInteger) self.viewItems.count) {
// [self.collectionView reloadItemsAtIndexPaths:@[ [NSIndexPath indexPathForRow:indexPath.row
// + 1
// inSection:0] ]];
// }
};
for (YapDatabaseViewRowChange *rowChange in messageRowChanges) { for (YapDatabaseViewRowChange *rowChange in messageRowChanges) {
switch (rowChange.type) { switch (rowChange.type) {
case YapDatabaseViewChangeDelete: { case YapDatabaseViewChangeDelete: {
DDLogError(@".... YapDatabaseViewChangeDelete: %@", rowChange.collectionKey); DDLogError(
@".... YapDatabaseViewChangeDelete: %@, %@", rowChange.collectionKey, rowChange.indexPath);
[self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]]; [self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]];
[rowsThatChangedSize removeObject:@(rowChange.indexPath.row)];
YapCollectionKey *collectionKey = rowChange.collectionKey; YapCollectionKey *collectionKey = rowChange.collectionKey;
OWSAssert(collectionKey.key.length > 0); OWSAssert(collectionKey.key.length > 0);
reloadNeighbors(rowChange.indexPath);
break; break;
} }
case YapDatabaseViewChangeInsert: { case YapDatabaseViewChangeInsert: {
DDLogError(@".... YapDatabaseViewChangeInsert: %@", rowChange.collectionKey); DDLogError(
@".... YapDatabaseViewChangeInsert: %@, %@", rowChange.collectionKey, rowChange.newIndexPath);
[self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]]; [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]];
[rowsThatChangedSize removeObject:@(rowChange.newIndexPath.row)];
TSInteraction *interaction = [self interactionAtIndexPath:rowChange.newIndexPath]; TSInteraction *interaction = [self interactionAtIndexPath:rowChange.newIndexPath];
if ([interaction isKindOfClass:[TSOutgoingMessage class]]) { if ([interaction isKindOfClass:[TSOutgoingMessage class]]) {
@ -2821,19 +2812,20 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
shouldAnimateScrollToBottom = NO; shouldAnimateScrollToBottom = NO;
} }
} }
reloadNeighbors(rowChange.newIndexPath);
break; break;
} }
case YapDatabaseViewChangeMove: { case YapDatabaseViewChangeMove: {
DDLogError(@".... YapDatabaseViewChangeMove: %@", rowChange.collectionKey); DDLogError(@".... YapDatabaseViewChangeMove: %@, %@, %@",
rowChange.collectionKey,
rowChange.indexPath,
rowChange.newIndexPath);
[self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]]; [self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]];
[self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]]; [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]];
reloadNeighbors(rowChange.indexPath);
reloadNeighbors(rowChange.newIndexPath);
break; break;
} }
case YapDatabaseViewChangeUpdate: { case YapDatabaseViewChangeUpdate: {
DDLogError(@".... YapDatabaseViewChangeUpdate: %@", rowChange.collectionKey); DDLogError(
@".... YapDatabaseViewChangeUpdate: %@, %@", rowChange.collectionKey, rowChange.indexPath);
YapCollectionKey *collectionKey = rowChange.collectionKey; YapCollectionKey *collectionKey = rowChange.collectionKey;
OWSAssert(collectionKey.key.length > 0); OWSAssert(collectionKey.key.length > 0);
if (collectionKey.key) { if (collectionKey.key) {
@ -2841,11 +2833,19 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
[self reloadViewItem:viewItem]; [self reloadViewItem:viewItem];
} }
[self.collectionView reloadItemsAtIndexPaths:@[ rowChange.indexPath ]]; [self.collectionView reloadItemsAtIndexPaths:@[ rowChange.indexPath ]];
reloadNeighbors(rowChange.indexPath); [rowsThatChangedSize removeObject:@(rowChange.indexPath.row)];
break; break;
} }
} }
} }
// The changes performed above may affect the size of neighboring cells,
// as they may affect which cells show "date" headers or "status" footers.
NSMutableArray<NSIndexPath *> *rowsToReload = [NSMutableArray new];
for (NSNumber *row in rowsThatChangedSize) {
[rowsToReload addObject:[NSIndexPath indexPathForRow:row.integerValue inSection:0]];
}
[self.collectionView reloadItemsAtIndexPaths:rowsToReload];
} }
completion:^(BOOL success) { completion:^(BOOL success) {
OWSAssert([NSThread isMainThread]); OWSAssert([NSThread isMainThread]);
@ -3595,7 +3595,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
return; return;
} }
text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (text.length < 1) { if (text.length < 1) {
return; return;
@ -3810,7 +3810,11 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
// This is a key method. It builds or rebuilds the list of // This is a key method. It builds or rebuilds the list of
// cell view models. // cell view models.
- (void)reloadViewItems //
// Returns a list of the rows which may have changed size and
// need to be reloaded if we're doing an incremental update
// of the view.
- (NSSet<NSNumber *> *)reloadViewItems
{ {
NSMutableArray<ConversationViewItem *> *viewItems = [NSMutableArray new]; NSMutableArray<ConversationViewItem *> *viewItems = [NSMutableArray new];
NSMutableDictionary<NSString *, ConversationViewItem *> *viewItemMap = [NSMutableDictionary new]; NSMutableDictionary<NSString *, ConversationViewItem *> *viewItemMap = [NSMutableDictionary new];
@ -3828,29 +3832,46 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
OWSAssert(interaction); OWSAssert(interaction);
ConversationViewItem *_Nullable viewItem = self.viewItemMap[interaction.uniqueId]; ConversationViewItem *_Nullable viewItem = self.viewItemMap[interaction.uniqueId];
if (!viewItem) { if (viewItem) {
viewItem.lastRow = viewItem.row;
} else {
viewItem = [[ConversationViewItem alloc] initWithTSInteraction:interaction]; viewItem = [[ConversationViewItem alloc] initWithTSInteraction:interaction];
} }
viewItem.row = (NSInteger)row;
[viewItems addObject:viewItem]; [viewItems addObject:viewItem];
OWSAssert(!viewItemMap[interaction.uniqueId]); OWSAssert(!viewItemMap[interaction.uniqueId]);
viewItemMap[interaction.uniqueId] = viewItem; viewItemMap[interaction.uniqueId] = viewItem;
} }
}]; }];
NSMutableSet<NSNumber *> *rowsThatChangedSize = [NSMutableSet new];
// Update the "shouldShowDate" property of the view items. // Update the "shouldShowDate" property of the view items.
int row = 0; BOOL shouldShowDateOnNextViewItem = YES;
BOOL shouldShowDateOnNextViewItem = NO;
uint64_t previousViewItemTimestamp = 0; uint64_t previousViewItemTimestamp = 0;
for (ConversationViewItem *viewItem in viewItems) { for (ConversationViewItem *viewItem in viewItems) {
if (row == 0) { BOOL canShowDate = NO;
viewItem.shouldShowDate = YES; switch (viewItem.interaction.interactionType) {
shouldShowDateOnNextViewItem = NO; case OWSInteractionType_Unknown:
} else if (viewItem.interaction.interactionType == OWSInteractionType_UnreadIndicator case OWSInteractionType_UnreadIndicator:
|| viewItem.interaction.interactionType == OWSInteractionType_Offer) { case OWSInteractionType_Offer:
viewItem.shouldShowDate = NO; canShowDate = NO;
break;
case OWSInteractionType_IncomingMessage:
case OWSInteractionType_OutgoingMessage:
case OWSInteractionType_Error:
case OWSInteractionType_Info:
case OWSInteractionType_Call:
canShowDate = YES;
break;
}
BOOL shouldShowDate = NO;
if (!canShowDate) {
shouldShowDate = NO;
shouldShowDateOnNextViewItem = YES; shouldShowDateOnNextViewItem = YES;
} else if (shouldShowDateOnNextViewItem) { } else if (shouldShowDateOnNextViewItem) {
viewItem.shouldShowDate = YES; shouldShowDate = YES;
shouldShowDateOnNextViewItem = NO; shouldShowDateOnNextViewItem = NO;
} else { } else {
uint64_t viewItemTimestamp = viewItem.interaction.timestampForSorting; uint64_t viewItemTimestamp = viewItem.interaction.timestampForSorting;
@ -3859,16 +3880,26 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
uint64_t timeDifferenceMs = viewItemTimestamp - previousViewItemTimestamp; uint64_t timeDifferenceMs = viewItemTimestamp - previousViewItemTimestamp;
static const uint64_t kShowTimeIntervalMs = 5 * kMinuteInMs; static const uint64_t kShowTimeIntervalMs = 5 * kMinuteInMs;
if (timeDifferenceMs > kShowTimeIntervalMs) { if (timeDifferenceMs > kShowTimeIntervalMs) {
viewItem.shouldShowDate = YES; shouldShowDate = YES;
} }
shouldShowDateOnNextViewItem = NO; shouldShowDateOnNextViewItem = NO;
} }
if (viewItem.shouldShowDate != shouldShowDate) {
// If this is an existing view item and it has changed size,
// note that so that we can reload this cell while doing
// incremental updates.
if (viewItem.lastRow != NSNotFound) {
[rowsThatChangedSize addObject:@(viewItem.lastRow)];
}
}
viewItem.shouldShowDate = shouldShowDate;
previousViewItemTimestamp = viewItem.interaction.timestampForSorting; previousViewItemTimestamp = viewItem.interaction.timestampForSorting;
row++;
} }
self.viewItems = viewItems; self.viewItems = viewItems;
self.viewItemMap = viewItemMap; self.viewItemMap = viewItemMap;
return [rowsThatChangedSize copy];
} }
// Whenever an interaction is modified, we need to reload it from the DB // Whenever an interaction is modified, we need to reload it from the DB

@ -41,6 +41,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
@property (nonatomic) BOOL shouldShowDate; @property (nonatomic) BOOL shouldShowDate;
@property (nonatomic) NSInteger row;
@property (nonatomic) NSInteger lastRow;
//@property (nonatomic, weak) ConversationViewCell *lastCell; //@property (nonatomic, weak) ConversationViewCell *lastCell;
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;

@ -70,6 +70,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
} }
_interaction = interaction; _interaction = interaction;
self.row = NSNotFound;
self.lastRow = NSNotFound;
return self; return self;
} }
@ -265,13 +267,14 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
if (!displayableText) { if (!displayableText) {
// Only show up to 2kb of text. // Only show up to 2kb of text.
const NSUInteger kMaxTextDisplayLength = 2 * 1024; const NSUInteger kMaxTextDisplayLength = 2 * 1024;
text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
displayableText = [[DisplayableTextFilter new] displayableText:text]; displayableText = [[DisplayableTextFilter new] displayableText:text];
if (displayableText.length > kMaxTextDisplayLength) { if (displayableText.length > kMaxTextDisplayLength) {
// Trim whitespace before _AND_ after slicing the snipper from the string. // Trim whitespace before _AND_ after slicing the snipper from the string.
NSString *snippet = NSString *snippet =
[[[displayableText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] [[[displayableText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]
substringWithRange:NSMakeRange(0, kMaxTextDisplayLength)] substringWithRange:NSMakeRange(0, kMaxTextDisplayLength)]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
displayableText = [NSString stringWithFormat:NSLocalizedString(@"OVERSIZE_TEXT_DISPLAY_FORMAT", displayableText = [NSString stringWithFormat:NSLocalizedString(@"OVERSIZE_TEXT_DISPLAY_FORMAT",
@"A display format for oversize text messages."), @"A display format for oversize text messages."),
snippet]; snippet];

@ -143,7 +143,7 @@
- (void)searchTextDidChange - (void)searchTextDidChange
{ {
NSString *searchText = NSString *searchText =
[self.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; [self.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
self.countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:searchText]; self.countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:searchText];

@ -515,8 +515,8 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68;
- (TSGroupModel *)makeGroup - (TSGroupModel *)makeGroup
{ {
NSString *groupName = NSString *groupName = [self.groupNameTextField.text
[self.groupNameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSMutableArray<NSString *> *recipientIds = [self.memberRecipientIds.allObjects mutableCopy]; NSMutableArray<NSString *> *recipientIds = [self.memberRecipientIds.allObjects mutableCopy];
[recipientIds addObject:[self.contactsViewHelper localNumber]]; [recipientIds addObject:[self.contactsViewHelper localNumber]];
NSData *groupId = [SecurityUtils generateRandomBytes:16]; NSData *groupId = [SecurityUtils generateRandomBytes:16];

@ -410,7 +410,7 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat
- (NSString *)normalizedProfileName - (NSString *)normalizedProfileName
{ {
return [self.nameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; return [self.nameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
} }
- (void)updateProfileCompleted - (void)updateProfileCompleted

@ -281,7 +281,7 @@ NSString *const kKeychainKey_LastRegisteredPhoneNumber = @"kKeychainKey_LastRegi
- (void)sendCodeAction - (void)sendCodeAction
{ {
NSString *phoneNumberText = NSString *phoneNumberText =
[_phoneNumberTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; [_phoneNumberTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (phoneNumberText.length < 1) { if (phoneNumberText.length < 1) {
[OWSAlerts [OWSAlerts
showAlertWithTitle:NSLocalizedString(@"REGISTRATION_VIEW_NO_PHONE_NUMBER_ALERT_TITLE", showAlertWithTitle:NSLocalizedString(@"REGISTRATION_VIEW_NO_PHONE_NUMBER_ALERT_TITLE",

@ -196,8 +196,8 @@ NS_ASSUME_NONNULL_BEGIN
UITextField *groupNameTextField = [UITextField new]; UITextField *groupNameTextField = [UITextField new];
_groupNameTextField = groupNameTextField; _groupNameTextField = groupNameTextField;
self.groupNameTextField.text = self.groupNameTextField.text = [self.thread.groupModel.groupName
[self.thread.groupModel.groupName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
groupNameTextField.textColor = [UIColor blackColor]; groupNameTextField.textColor = [UIColor blackColor];
groupNameTextField.font = [UIFont ows_dynamicTypeTitle2Font]; groupNameTextField.font = [UIFont ows_dynamicTypeTitle2Font];
groupNameTextField.placeholder groupNameTextField.placeholder
@ -373,8 +373,8 @@ NS_ASSUME_NONNULL_BEGIN
{ {
OWSAssert(self.conversationSettingsViewDelegate); OWSAssert(self.conversationSettingsViewDelegate);
NSString *groupName = NSString *groupName = [self.groupNameTextField.text
[self.groupNameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:groupName TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:groupName
memberIds:[self.memberRecipientIds.allObjects mutableCopy] memberIds:[self.memberRecipientIds.allObjects mutableCopy]
image:self.groupAvatar image:self.groupAvatar

@ -272,7 +272,8 @@ NSString *const kNotificationsManagerNewMesssageSoundName = @"NewMessage.aifc";
BOOL shouldPlaySound = [self shouldPlaySoundForNotification]; BOOL shouldPlaySound = [self shouldPlaySoundForNotification];
NSString *senderName = [contactsManager displayNameForPhoneIdentifier:message.authorId]; NSString *senderName = [contactsManager displayNameForPhoneIdentifier:message.authorId];
NSString *groupName = [thread.name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; NSString *groupName =
[thread.name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (groupName.length < 1) { if (groupName.length < 1) {
groupName = [MessageStrings newGroupDefaultTitle]; groupName = [MessageStrings newGroupDefaultTitle];
} }

@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSString *)ows_stripped - (NSString *)ows_stripped
{ {
return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
} }
- (NSString *)rtlSafeAppend:(NSString *)string referenceView:(UIView *)referenceView - (NSString *)rtlSafeAppend:(NSString *)string referenceView:(UIView *)referenceView

@ -136,7 +136,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSString *)trimName:(NSString *)name - (NSString *)trimName:(NSString *)name
{ {
return [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; return [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
} }
+ (NSString *)uniqueIdFromABRecordId:(ABRecordID)recordId + (NSString *)uniqueIdFromABRecordId:(ABRecordID)recordId

Loading…
Cancel
Save