|
|
|
@ -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 () <UITableViewDelegate,
|
|
|
|
@ -76,6 +93,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
|
|
|
|
|
// Views
|
|
|
|
|
|
|
|
|
|
@property (nonatomic, readonly) UIStackView *reminderStackView;
|
|
|
|
|
@property (nonatomic, readonly) UITableViewCell *reminderViewCell;
|
|
|
|
|
@property (nonatomic, readonly) UIView *deregisteredView;
|
|
|
|
|
@property (nonatomic, readonly) UIView *outageView;
|
|
|
|
|
@property (nonatomic, readonly) UIView *archiveReminderView;
|
|
|
|
@ -84,6 +103,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
@property (nonatomic) TSThread *lastThread;
|
|
|
|
|
|
|
|
|
|
@property (nonatomic) BOOL hasArchivedThreadsRow;
|
|
|
|
|
@property (nonatomic) BOOL hasVisibleReminders;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
@ -245,18 +265,13 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIStackView *reminderStackView = [UIStackView new];
|
|
|
|
|
_reminderStackView = reminderStackView;
|
|
|
|
|
reminderStackView.axis = UILayoutConstraintAxisVertical;
|
|
|
|
|
reminderStackView.spacing = 0;
|
|
|
|
|
[self.view addSubview:reminderStackView];
|
|
|
|
|
[reminderStackView autoPinWidthToSuperview];
|
|
|
|
|
[reminderStackView autoPinToTopLayoutGuideOfViewController:self withInset:0];
|
|
|
|
|
|
|
|
|
|
// Fixes ambiguous height of an empty stack view pinned above a scroll view on iOS10.
|
|
|
|
|
// Without this users would sometimes see the empty stackview take up most of their screen.
|
|
|
|
|
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow
|
|
|
|
|
forConstraints:^{
|
|
|
|
|
[reminderStackView autoSetDimension:ALDimensionHeight toSize:0];
|
|
|
|
|
}];
|
|
|
|
|
_reminderViewCell = [UITableViewCell new];
|
|
|
|
|
self.reminderViewCell.selectionStyle = UITableViewCellSelectionStyleNone;
|
|
|
|
|
[self.reminderViewCell.contentView addSubview:reminderStackView];
|
|
|
|
|
[reminderStackView autoPinEdgesToSuperviewEdges];
|
|
|
|
|
|
|
|
|
|
__weak HomeViewController *weakSelf = self;
|
|
|
|
|
ReminderView *deregisteredView =
|
|
|
|
@ -300,11 +315,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
[self.tableView registerClass:[HomeViewCell class] forCellReuseIdentifier:HomeViewCell.cellReuseIdentifier];
|
|
|
|
|
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kArchivedConversationsReuseIdentifier];
|
|
|
|
|
[self.view addSubview:self.tableView];
|
|
|
|
|
[self.tableView autoPinWidthToSuperview];
|
|
|
|
|
[self.tableView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
|
|
|
|
[self.tableView autoPinEdgesToSuperviewEdges];
|
|
|
|
|
|
|
|
|
|
// TODO - have content scroll behind navbar will require changing this.
|
|
|
|
|
[self.tableView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:reminderStackView];
|
|
|
|
|
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
|
|
|
|
self.tableView.estimatedRowHeight = 60;
|
|
|
|
|
|
|
|
|
@ -340,6 +352,9 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
self.missingContactsPermissionView.hidden = !self.contactsManager.isSystemContactsDenied;
|
|
|
|
|
self.deregisteredView.hidden = !TSAccountManager.sharedInstance.isDeregistered;
|
|
|
|
|
self.outageView.hidden = !OutageDetection.sharedManager.hasOutage;
|
|
|
|
|
|
|
|
|
|
self.hasVisibleReminders = !self.archiveReminderView.isHidden || !self.missingContactsPermissionView.isHidden
|
|
|
|
|
|| !self.deregisteredView.isHidden || !self.outageView.isHidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)viewDidLoad
|
|
|
|
@ -402,9 +417,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
self.searchResultsController = searchResultsController;
|
|
|
|
|
[self addChildViewController:searchResultsController];
|
|
|
|
|
[self.view addSubview:searchResultsController.view];
|
|
|
|
|
[searchResultsController.view autoPinWidthToSuperview];
|
|
|
|
|
[searchResultsController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:searchBar];
|
|
|
|
|
[searchResultsController.view autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.tableView];
|
|
|
|
|
[searchResultsController.view autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero excludingEdge:ALEdgeTop];
|
|
|
|
|
[searchResultsController.view autoPinTopToSuperviewMarginWithInset:56];
|
|
|
|
|
searchResultsController.view.hidden = YES;
|
|
|
|
|
|
|
|
|
|
[self updateBarButtonItems];
|
|
|
|
@ -470,11 +484,14 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
{
|
|
|
|
|
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
|
|
|
|
|
|
|
|
|
|
if ([self isIndexPathForArchivedConversations:indexPath]) {
|
|
|
|
|
if (!indexPath) {
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (indexPath.section != HomeViewControllerSectionConversations) {
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (indexPath) {
|
|
|
|
|
[previewingContext setSourceRect:[self.tableView rectForRowAtIndexPath:indexPath]];
|
|
|
|
|
|
|
|
|
|
ConversationViewController *vc = [ConversationViewController new];
|
|
|
|
@ -484,9 +501,6 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
[vc peekSetup];
|
|
|
|
|
|
|
|
|
|
return vc;
|
|
|
|
|
} else {
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)previewingContext:(id<UIViewControllerPreviewing>)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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)isIndexPathForArchivedConversations:(NSIndexPath *)indexPath
|
|
|
|
|
{
|
|
|
|
|
if (self.homeViewMode != HomeViewMode_Inbox) {
|
|
|
|
|
return NO;
|
|
|
|
|
case HomeViewControllerSectionArchiveButton: {
|
|
|
|
|
return self.hasArchivedThreadsRow ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
|
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,10 +898,12 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
|
|
|
|
|
- (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
|
|
|
{
|
|
|
|
|
if ([self isIndexPathForArchivedConversations:indexPath]) {
|
|
|
|
|
HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section;
|
|
|
|
|
switch (section) {
|
|
|
|
|
case HomeViewControllerSectionReminders: {
|
|
|
|
|
return @[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case HomeViewControllerSectionConversations: {
|
|
|
|
|
UITableViewRowAction *deleteAction =
|
|
|
|
|
[UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault
|
|
|
|
|
title:NSLocalizedString(@"TXT_DELETE_TITLE", nil)
|
|
|
|
@ -901,30 +926,42 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
archiveAction = [UITableViewRowAction
|
|
|
|
|
rowActionWithStyle:UITableViewRowActionStyleNormal
|
|
|
|
|
title:NSLocalizedString(@"UNARCHIVE_ACTION",
|
|
|
|
|
@"Pressing this button moves an archived thread from the archive back to the inbox")
|
|
|
|
|
@"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 @[];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
|
|
|
|
|
{
|
|
|
|
|
if ([self isIndexPathForArchivedConversations:indexPath]) {
|
|
|
|
|
HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section;
|
|
|
|
|
switch (section) {
|
|
|
|
|
case HomeViewControllerSectionReminders: {
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case HomeViewControllerSectionConversations: {
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
case HomeViewControllerSectionArchiveButton: {
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action
|
|
|
|
@ -1218,7 +1278,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
|
|
|
|
{
|
|
|
|
|
OWSAssertIsOnMainThread();
|
|
|
|
|
|
|
|
|
|
self.threadMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[ self.currentGrouping ]
|
|
|
|
|
self.threadMappings = [[YapDatabaseViewMappings alloc]
|
|
|
|
|
initWithGroups:@[ kReminderViewPseudoGroup, self.currentGrouping, kArchiveButtonPseudoGroup ]
|
|
|
|
|
view:TSThreadDatabaseViewExtensionName];
|
|
|
|
|
[self.threadMappings setIsReversed:YES forGroup:self.currentGrouping];
|
|
|
|
|
|
|
|
|
|