diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index ba50c92be..4249fe394 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -40,6 +40,23 @@ typedef NS_ENUM(NSInteger, HomeViewMode) { HomeViewMode_Inbox, }; +// The bulk of the content in this view is driven by a YapDB view/mapping. +// However, we also want to optionally include ReminderView's at the top +// and an "Archived Conversations" button at the bottom. Rather than introduce +// index-offsets into the Mapping calculation, we introduce two pseudo groups +// to add a top and bottom section to the content, and create cells for those +// sections without consulting the YapMapping. +// This is a bit of a hack, but it consolidates the hacks into the Reminder/Archive section +// and allows us to leaves the bulk of the content logic on the happy path. +NSString *const kReminderViewPseudoGroup = @"kReminderViewPseudoGroup"; +NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup"; + +typedef NS_ENUM(NSInteger, HomeViewControllerSection) { + HomeViewControllerSectionReminders, + HomeViewControllerSectionConversations, + HomeViewControllerSectionArchiveButton, +}; + NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversationsReuseIdentifier"; @interface HomeViewController () )previewingContext @@ -542,7 +556,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations BOOL isShowingSearchResults = !self.searchResultsController.view.hidden; if (isShowingSearchResults) { OWSAssert(self.searchBar.text.ows_stripped.length > 0); - self.tableView.contentOffset = CGPointZero; + [self scrollSearchBarToTopAnimated:NO]; } else if (self.lastThread) { OWSAssert(self.searchBar.text.ows_stripped.length == 0); @@ -734,31 +748,30 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations return (NSInteger)[self.threadMappings numberOfSections]; } -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)aSection { - NSInteger result = (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)section]; - if (self.hasArchivedThreadsRow) { - // Add the "archived conversations" row. - result++; + HomeViewControllerSection section = (HomeViewControllerSection)aSection; + switch (section) { + case HomeViewControllerSectionReminders: { + return self.hasVisibleReminders ? 1 : 0; + } + case HomeViewControllerSectionConversations: { + NSInteger result = (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)section]; + return result; + } + case HomeViewControllerSectionArchiveButton: { + return self.hasArchivedThreadsRow ? 1 : 0; + } } - return result; -} -- (BOOL)isIndexPathForArchivedConversations:(NSIndexPath *)indexPath -{ - if (self.homeViewMode != HomeViewMode_Inbox) { - return NO; - } - if (indexPath.section != 0) { - return NO; - } - NSInteger cellCount = (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)0]; - return indexPath.row == cellCount; + OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)section); + return 0; } - (ThreadViewModel *)threadViewModelForIndexPath:(NSIndexPath *)indexPath { TSThread *threadRecord = [self threadForIndexPath:indexPath]; + OWSAssert(threadRecord); ThreadViewModel *_Nullable cachedThreadViewModel = [self.threadViewModelCache objectForKey:threadRecord.uniqueId]; if (cachedThreadViewModel) { @@ -775,11 +788,21 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - if ([self isIndexPathForArchivedConversations:indexPath]) { - return [self cellForArchivedConversationsRow:tableView]; - } else { - return [self tableView:tableView cellForConversationAtIndexPath:indexPath]; + HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; + switch (section) { + case HomeViewControllerSectionReminders: { + return self.reminderViewCell; + } + case HomeViewControllerSectionConversations: { + return [self tableView:tableView cellForConversationAtIndexPath:indexPath]; + } + case HomeViewControllerSectionArchiveButton: { + return [self cellForArchivedConversationsRow:tableView]; + } } + + OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)section); + return [UITableViewCell new]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForConversationAtIndexPath:(NSIndexPath *)indexPath @@ -875,56 +898,70 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations - (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath { - if ([self isIndexPathForArchivedConversations:indexPath]) { - return @[]; - } - - UITableViewRowAction *deleteAction = - [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault - title:NSLocalizedString(@"TXT_DELETE_TITLE", nil) - handler:^(UITableViewRowAction *action, NSIndexPath *swipedIndexPath) { - [self tableViewCellTappedDelete:swipedIndexPath]; - }]; - - UITableViewRowAction *archiveAction; - if (self.homeViewMode == HomeViewMode_Inbox) { - archiveAction = [UITableViewRowAction - rowActionWithStyle:UITableViewRowActionStyleNormal - title:NSLocalizedString(@"ARCHIVE_ACTION", - @"Pressing this button moves a thread from the inbox to the archive") - handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) { - [self archiveIndexPath:tappedIndexPath]; - [Environment.preferences setHasArchivedAMessage:YES]; - }]; + HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; + switch (section) { + case HomeViewControllerSectionReminders: { + return @[]; + } + case HomeViewControllerSectionConversations: { + UITableViewRowAction *deleteAction = + [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault + title:NSLocalizedString(@"TXT_DELETE_TITLE", nil) + handler:^(UITableViewRowAction *action, NSIndexPath *swipedIndexPath) { + [self tableViewCellTappedDelete:swipedIndexPath]; + }]; + + UITableViewRowAction *archiveAction; + if (self.homeViewMode == HomeViewMode_Inbox) { + archiveAction = [UITableViewRowAction + rowActionWithStyle:UITableViewRowActionStyleNormal + title:NSLocalizedString(@"ARCHIVE_ACTION", + @"Pressing this button moves a thread from the inbox to the archive") + handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) { + [self archiveIndexPath:tappedIndexPath]; + [Environment.preferences setHasArchivedAMessage:YES]; + }]; + + } else { + archiveAction = [UITableViewRowAction + rowActionWithStyle:UITableViewRowActionStyleNormal + title:NSLocalizedString(@"UNARCHIVE_ACTION", + @"Pressing this button moves an archived thread from the archive back to " + @"the inbox") + handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) { + [self archiveIndexPath:tappedIndexPath]; + }]; + } - } else { - archiveAction = [UITableViewRowAction - rowActionWithStyle:UITableViewRowActionStyleNormal - title:NSLocalizedString(@"UNARCHIVE_ACTION", - @"Pressing this button moves an archived thread from the archive back to the inbox") - handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) { - [self archiveIndexPath:tappedIndexPath]; - }]; + return @[ deleteAction, archiveAction ]; + } + case HomeViewControllerSectionArchiveButton: { + return @[]; + } } - - - return @[ deleteAction, archiveAction ]; } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { - if ([self isIndexPathForArchivedConversations:indexPath]) { - return NO; + HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; + switch (section) { + case HomeViewControllerSectionReminders: { + return NO; + } + case HomeViewControllerSectionConversations: { + return YES; + } + case HomeViewControllerSectionArchiveButton: { + return NO; + } } - - return YES; } #pragma mark - UISearchBarDelegate - (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar { - [self.tableView setContentOffset:CGPointZero animated:NO]; + [self scrollSearchBarToTopAnimated:NO]; [self updateSearchResultsVisibility]; @@ -977,13 +1014,19 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations self.searchResultsController.view.hidden = !isSearching; if (isSearching) { - [self.tableView setContentOffset:CGPointZero animated:NO]; + [self scrollSearchBarToTopAnimated:NO]; self.tableView.scrollEnabled = NO; } else { self.tableView.scrollEnabled = YES; } } +- (void)scrollSearchBarToTopAnimated:(BOOL)isAnimated +{ + CGFloat topInset = self.topLayoutGuide.length; + [self.tableView setContentOffset:CGPointMake(0, -topInset) animated:isAnimated]; +} + #pragma mark - UIScrollViewDelegate - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView @@ -1004,6 +1047,11 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations - (void)tableViewCellTappedDelete:(NSIndexPath *)indexPath { + if (indexPath.section != HomeViewControllerSectionConversations) { + OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)indexPath.section); + return; + } + TSThread *thread = [self threadForIndexPath:indexPath]; if ([thread isKindOfClass:[TSGroupThread class]]) { @@ -1055,6 +1103,11 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations - (void)archiveIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section != HomeViewControllerSectionConversations) { + OWSFail(@"%@ failure: unexpected section: %lu", self.logTag, (unsigned long)indexPath.section); + return; + } + TSThread *thread = [self threadForIndexPath:indexPath]; [self.editingDbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { @@ -1075,15 +1128,22 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations DDLogInfo(@"%@ %s %ld %ld", self.logTag, __PRETTY_FUNCTION__, (long)indexPath.row, (long)indexPath.section); [self.searchBar resignFirstResponder]; - - if ([self isIndexPathForArchivedConversations:indexPath]) { - [self showArchivedConversations]; - return; + HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; + switch (section) { + case HomeViewControllerSectionReminders: { + break; + } + case HomeViewControllerSectionConversations: { + TSThread *thread = [self threadForIndexPath:indexPath]; + [self presentThread:thread action:ConversationViewActionNone]; + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + break; + } + case HomeViewControllerSectionArchiveButton: { + [self showArchivedConversations]; + break; + } } - - TSThread *thread = [self threadForIndexPath:indexPath]; - [self presentThread:thread action:ConversationViewActionNone]; - [tableView deselectRowAtIndexPath:indexPath animated:YES]; } - (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action @@ -1218,8 +1278,9 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations { OWSAssertIsOnMainThread(); - self.threadMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[ self.currentGrouping ] - view:TSThreadDatabaseViewExtensionName]; + self.threadMappings = [[YapDatabaseViewMappings alloc] + initWithGroups:@[ kReminderViewPseudoGroup, self.currentGrouping, kArchiveButtonPseudoGroup ] + view:TSThreadDatabaseViewExtensionName]; [self.threadMappings setIsReversed:YES forGroup:self.currentGrouping]; [self resetMappings];