2014-10-29 21:58:58 +01:00
|
|
|
//
|
2018-01-10 16:54:17 +01:00
|
|
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
2014-10-29 21:58:58 +01:00
|
|
|
//
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
#import "HomeViewController.h"
|
2016-10-10 22:02:09 +02:00
|
|
|
#import "AppDelegate.h"
|
2017-07-31 22:45:06 +02:00
|
|
|
#import "AppSettingsViewController.h"
|
2018-04-10 19:02:33 +02:00
|
|
|
#import "HomeViewCell.h"
|
2017-09-06 19:55:01 +02:00
|
|
|
#import "NewContactThreadViewController.h"
|
2017-08-17 18:37:21 +02:00
|
|
|
#import "OWSNavigationController.h"
|
2018-03-05 15:30:58 +01:00
|
|
|
#import "OWSPrimaryStorage.h"
|
2017-08-16 16:25:36 +02:00
|
|
|
#import "ProfileViewController.h"
|
2016-10-10 22:02:09 +02:00
|
|
|
#import "PushManager.h"
|
2018-06-18 17:28:21 +02:00
|
|
|
#import "RegistrationUtils.h"
|
2016-10-10 22:02:09 +02:00
|
|
|
#import "Signal-Swift.h"
|
2017-12-04 16:35:47 +01:00
|
|
|
#import "SignalApp.h"
|
2015-01-14 22:30:01 +01:00
|
|
|
#import "TSAccountManager.h"
|
2014-11-21 14:38:37 +01:00
|
|
|
#import "TSDatabaseView.h"
|
2016-08-01 00:25:07 +02:00
|
|
|
#import "TSGroupThread.h"
|
2017-07-12 16:46:54 +02:00
|
|
|
#import "ViewControllerUtils.h"
|
2017-05-09 16:45:41 +02:00
|
|
|
#import <PromiseKit/AnyPromise.h>
|
2017-12-19 03:50:51 +01:00
|
|
|
#import <SignalMessaging/OWSContactsManager.h>
|
2017-12-01 23:10:14 +01:00
|
|
|
#import <SignalMessaging/OWSFormat.h>
|
2017-12-08 17:50:35 +01:00
|
|
|
#import <SignalMessaging/UIUtil.h>
|
2017-09-22 16:30:35 +02:00
|
|
|
#import <SignalServiceKit/NSDate+OWS.h>
|
2017-04-08 16:47:47 +02:00
|
|
|
#import <SignalServiceKit/OWSBlockingManager.h>
|
2016-10-14 22:59:58 +02:00
|
|
|
#import <SignalServiceKit/OWSMessageSender.h>
|
2018-01-30 21:49:36 +01:00
|
|
|
#import <SignalServiceKit/OWSMessageUtils.h>
|
2018-06-15 18:52:45 +02:00
|
|
|
#import <SignalServiceKit/TSAccountManager.h>
|
2016-10-14 22:59:58 +02:00
|
|
|
#import <SignalServiceKit/TSOutgoingMessage.h>
|
2017-08-03 19:16:45 +02:00
|
|
|
#import <SignalServiceKit/Threading.h>
|
2018-08-07 23:36:34 +02:00
|
|
|
#import <StoreKit/StoreKit.h>
|
2017-12-20 17:28:07 +01:00
|
|
|
#import <YapDatabase/YapDatabase.h>
|
2014-11-21 14:38:37 +01:00
|
|
|
#import <YapDatabase/YapDatabaseViewChange.h>
|
2016-10-14 22:59:58 +02:00
|
|
|
#import <YapDatabase/YapDatabaseViewConnection.h>
|
2014-11-21 14:38:37 +01:00
|
|
|
|
2018-06-12 17:27:32 +02:00
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
2018-04-23 18:33:23 +02:00
|
|
|
typedef NS_ENUM(NSInteger, HomeViewMode) {
|
|
|
|
HomeViewMode_Archive,
|
|
|
|
HomeViewMode_Inbox,
|
|
|
|
};
|
|
|
|
|
2018-07-31 02:31:08 +02:00
|
|
|
// 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,
|
|
|
|
};
|
|
|
|
|
2018-04-23 18:33:23 +02:00
|
|
|
NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversationsReuseIdentifier";
|
2017-07-14 20:06:19 +02:00
|
|
|
|
2018-06-11 18:15:46 +02:00
|
|
|
@interface HomeViewController () <UITableViewDelegate,
|
|
|
|
UITableViewDataSource,
|
|
|
|
UIViewControllerPreviewingDelegate,
|
2018-08-21 22:56:21 +02:00
|
|
|
UISearchBarDelegate,
|
2018-06-21 15:59:01 +02:00
|
|
|
ConversationSearchViewDelegate>
|
2017-07-10 22:04:03 +02:00
|
|
|
|
|
|
|
@property (nonatomic) UITableView *tableView;
|
|
|
|
@property (nonatomic) UILabel *emptyBoxLabel;
|
2015-01-29 03:57:53 +01:00
|
|
|
|
2017-04-08 16:47:47 +02:00
|
|
|
@property (nonatomic) YapDatabaseConnection *editingDbConnection;
|
|
|
|
@property (nonatomic) YapDatabaseConnection *uiDatabaseConnection;
|
|
|
|
@property (nonatomic) YapDatabaseViewMappings *threadMappings;
|
2018-04-23 18:33:23 +02:00
|
|
|
@property (nonatomic) HomeViewMode homeViewMode;
|
2017-04-08 16:47:47 +02:00
|
|
|
@property (nonatomic) id previewingContext;
|
2017-05-05 18:39:21 +02:00
|
|
|
@property (nonatomic) NSSet<NSString *> *blockedPhoneNumberSet;
|
2018-04-23 18:15:21 +02:00
|
|
|
@property (nonatomic, readonly) NSCache<NSString *, ThreadViewModel *> *threadViewModelCache;
|
2017-05-31 23:49:21 +02:00
|
|
|
@property (nonatomic) BOOL isViewVisible;
|
|
|
|
@property (nonatomic) BOOL shouldObserveDBModifications;
|
2018-08-07 23:36:34 +02:00
|
|
|
@property (nonatomic) BOOL hasEverAppeared;
|
2017-05-31 23:49:21 +02:00
|
|
|
|
2018-06-08 03:51:42 +02:00
|
|
|
// Mark: Search
|
|
|
|
|
2018-08-21 22:56:21 +02:00
|
|
|
@property (nonatomic, readonly) UISearchBar *searchBar;
|
2018-06-09 12:52:03 +02:00
|
|
|
@property (nonatomic) ConversationSearchViewController *searchResultsController;
|
2018-06-08 03:51:42 +02:00
|
|
|
|
2017-02-27 17:04:14 +01:00
|
|
|
// Dependencies
|
|
|
|
|
2017-04-08 16:47:47 +02:00
|
|
|
@property (nonatomic, readonly) AccountManager *accountManager;
|
2016-09-21 14:37:51 +02:00
|
|
|
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
|
2017-04-08 16:47:47 +02:00
|
|
|
@property (nonatomic, readonly) OWSMessageSender *messageSender;
|
|
|
|
@property (nonatomic, readonly) OWSBlockingManager *blockingManager;
|
|
|
|
|
2017-05-05 18:39:21 +02:00
|
|
|
// Views
|
|
|
|
|
2018-07-31 02:31:08 +02:00
|
|
|
@property (nonatomic, readonly) UIStackView *reminderStackView;
|
|
|
|
@property (nonatomic, readonly) UITableViewCell *reminderViewCell;
|
2018-06-22 19:42:59 +02:00
|
|
|
@property (nonatomic, readonly) UIView *deregisteredView;
|
|
|
|
@property (nonatomic, readonly) UIView *outageView;
|
|
|
|
@property (nonatomic, readonly) UIView *archiveReminderView;
|
|
|
|
@property (nonatomic, readonly) UIView *missingContactsPermissionView;
|
2017-07-10 22:04:03 +02:00
|
|
|
|
|
|
|
@property (nonatomic) TSThread *lastThread;
|
2014-10-29 21:58:58 +01:00
|
|
|
|
2018-04-26 21:56:56 +02:00
|
|
|
@property (nonatomic) BOOL hasArchivedThreadsRow;
|
2018-07-23 21:03:07 +02:00
|
|
|
@property (nonatomic) BOOL hasThemeChanged;
|
2018-07-31 02:31:08 +02:00
|
|
|
@property (nonatomic) BOOL hasVisibleReminders;
|
2018-04-26 21:56:56 +02:00
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
@end
|
|
|
|
|
2017-05-31 22:37:08 +02:00
|
|
|
#pragma mark -
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
@implementation HomeViewController
|
2014-10-29 21:58:58 +01:00
|
|
|
|
2017-05-05 18:39:21 +02:00
|
|
|
#pragma mark - Init
|
|
|
|
|
2016-09-21 14:37:51 +02:00
|
|
|
- (instancetype)init
|
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (!self) {
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2018-04-23 18:33:23 +02:00
|
|
|
_homeViewMode = HomeViewMode_Inbox;
|
|
|
|
|
2016-11-12 18:22:29 +01:00
|
|
|
[self commonInit];
|
2016-09-21 14:37:51 +02:00
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2018-06-12 17:27:32 +02:00
|
|
|
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
|
2016-09-21 14:37:51 +02:00
|
|
|
{
|
2018-08-27 16:29:51 +02:00
|
|
|
OWSFailDebug(@"Do not load this from the storyboard.");
|
2017-07-10 22:04:03 +02:00
|
|
|
|
2016-09-21 14:37:51 +02:00
|
|
|
self = [super initWithCoder:aDecoder];
|
|
|
|
if (!self) {
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2016-11-12 18:22:29 +01:00
|
|
|
[self commonInit];
|
2016-09-21 14:37:51 +02:00
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2016-11-12 18:22:29 +01:00
|
|
|
- (void)commonInit
|
|
|
|
{
|
2017-12-04 16:35:47 +01:00
|
|
|
_accountManager = SignalApp.sharedApp.accountManager;
|
2018-08-31 19:16:31 +02:00
|
|
|
_contactsManager = Environment.shared.contactsManager;
|
|
|
|
_messageSender = Environment.shared.messageSender;
|
2017-04-08 16:47:47 +02:00
|
|
|
_blockingManager = [OWSBlockingManager sharedManager];
|
|
|
|
_blockedPhoneNumberSet = [NSSet setWithArray:[_blockingManager blockedPhoneNumbers]];
|
2018-04-23 18:15:21 +02:00
|
|
|
_threadViewModelCache = [NSCache new];
|
2017-04-08 16:47:47 +02:00
|
|
|
|
2017-12-07 20:31:00 +01:00
|
|
|
// Ensure ExperienceUpgradeFinder has been initialized.
|
2018-05-11 16:36:40 +02:00
|
|
|
#pragma GCC diagnostic push
|
|
|
|
#pragma GCC diagnostic ignored "-Wunused-result"
|
2017-12-12 22:31:03 +01:00
|
|
|
[ExperienceUpgradeFinder sharedManager];
|
2018-05-11 16:36:40 +02:00
|
|
|
#pragma GCC diagnostic pop
|
2017-04-08 16:47:47 +02:00
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(blockedPhoneNumbersDidChange:)
|
|
|
|
name:kNSNotificationName_BlockedPhoneNumbersDidChange
|
|
|
|
object:nil];
|
2017-05-01 20:10:53 +02:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(signalAccountsDidChange:)
|
|
|
|
name:OWSContactsManagerSignalAccountsDidChangeNotification
|
|
|
|
object:nil];
|
2017-05-31 23:49:21 +02:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(applicationWillEnterForeground:)
|
2018-01-10 16:54:17 +01:00
|
|
|
name:OWSApplicationWillEnterForegroundNotification
|
2017-05-31 23:49:21 +02:00
|
|
|
object:nil];
|
2017-11-17 19:46:14 +01:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(applicationDidBecomeActive:)
|
2018-01-10 16:54:17 +01:00
|
|
|
name:OWSApplicationDidBecomeActiveNotification
|
2017-11-17 19:46:14 +01:00
|
|
|
object:nil];
|
2018-05-31 17:11:01 +02:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(applicationWillResignActive:)
|
|
|
|
name:OWSApplicationWillResignActiveNotification
|
|
|
|
object:nil];
|
2017-07-14 19:43:36 +02:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(yapDatabaseModified:)
|
|
|
|
name:YapDatabaseModifiedNotification
|
2018-03-05 15:30:58 +01:00
|
|
|
object:OWSPrimaryStorage.sharedManager.dbNotificationObject];
|
2017-11-29 17:37:49 +01:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
2017-12-12 22:31:03 +01:00
|
|
|
selector:@selector(yapDatabaseModifiedExternally:)
|
2017-11-29 17:37:49 +01:00
|
|
|
name:YapDatabaseModifiedExternallyNotification
|
2018-01-10 17:37:26 +01:00
|
|
|
object:nil];
|
2018-06-15 18:52:45 +02:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(deregistrationStateDidChange:)
|
|
|
|
name:DeregistrationStateDidChangeNotification
|
|
|
|
object:nil];
|
2018-06-19 18:09:44 +02:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(outageStateDidChange:)
|
|
|
|
name:OutageDetection.outageStateDidChange
|
|
|
|
object:nil];
|
2018-07-12 23:45:29 +02:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(themeDidChange:)
|
2018-07-23 21:03:07 +02:00
|
|
|
name:ThemeDidChangeNotification
|
2018-07-12 23:45:29 +02:00
|
|
|
object:nil];
|
2017-04-08 16:47:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
}
|
|
|
|
|
2017-05-05 18:39:21 +02:00
|
|
|
#pragma mark - Notifications
|
|
|
|
|
2017-04-08 16:47:47 +02:00
|
|
|
- (void)blockedPhoneNumbersDidChange:(id)notification
|
|
|
|
{
|
2017-12-19 17:38:25 +01:00
|
|
|
OWSAssertIsOnMainThread();
|
2017-06-09 22:21:59 +02:00
|
|
|
|
|
|
|
_blockedPhoneNumberSet = [NSSet setWithArray:[_blockingManager blockedPhoneNumbers]];
|
|
|
|
|
2018-04-21 21:22:40 +02:00
|
|
|
[self reloadTableViewData];
|
2016-11-12 18:22:29 +01:00
|
|
|
}
|
|
|
|
|
2017-05-01 20:10:53 +02:00
|
|
|
- (void)signalAccountsDidChange:(id)notification
|
|
|
|
{
|
2017-12-19 17:38:25 +01:00
|
|
|
OWSAssertIsOnMainThread();
|
2017-06-09 22:21:59 +02:00
|
|
|
|
2018-04-21 21:22:40 +02:00
|
|
|
[self reloadTableViewData];
|
2017-05-01 20:10:53 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 18:52:45 +02:00
|
|
|
- (void)deregistrationStateDidChange:(id)notification
|
|
|
|
{
|
|
|
|
OWSAssertIsOnMainThread();
|
|
|
|
|
2018-06-15 19:39:35 +02:00
|
|
|
[self updateReminderViews];
|
2018-06-15 18:52:45 +02:00
|
|
|
}
|
|
|
|
|
2018-06-19 18:09:44 +02:00
|
|
|
- (void)outageStateDidChange:(id)notification
|
|
|
|
{
|
|
|
|
OWSAssertIsOnMainThread();
|
|
|
|
|
|
|
|
[self updateReminderViews];
|
|
|
|
}
|
|
|
|
|
2018-07-13 00:01:43 +02:00
|
|
|
#pragma mark - Theme
|
|
|
|
|
2018-08-08 21:49:22 +02:00
|
|
|
- (void)themeDidChange:(NSNotification *)notification
|
2018-07-12 23:45:29 +02:00
|
|
|
{
|
|
|
|
OWSAssertIsOnMainThread();
|
|
|
|
|
|
|
|
[self applyTheme];
|
|
|
|
[self.tableView reloadData];
|
2018-07-23 21:03:07 +02:00
|
|
|
|
|
|
|
self.hasThemeChanged = YES;
|
2018-07-12 23:45:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)applyTheme
|
|
|
|
{
|
|
|
|
OWSAssertIsOnMainThread();
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(self.tableView);
|
|
|
|
OWSAssertDebug(self.searchBar);
|
2018-07-12 23:45:29 +02:00
|
|
|
|
2018-07-13 15:50:49 +02:00
|
|
|
self.view.backgroundColor = Theme.backgroundColor;
|
|
|
|
self.tableView.backgroundColor = Theme.backgroundColor;
|
2018-07-12 23:45:29 +02:00
|
|
|
}
|
|
|
|
|
2017-05-05 18:39:21 +02:00
|
|
|
#pragma mark - View Life Cycle
|
|
|
|
|
2017-07-10 22:04:03 +02:00
|
|
|
- (void)loadView
|
2016-09-21 14:37:51 +02:00
|
|
|
{
|
2017-07-10 22:04:03 +02:00
|
|
|
[super loadView];
|
|
|
|
|
|
|
|
// TODO: Remove this.
|
2018-04-26 21:31:36 +02:00
|
|
|
if (self.homeViewMode == HomeViewMode_Inbox) {
|
|
|
|
[SignalApp.sharedApp setHomeViewController:self];
|
|
|
|
}
|
2017-07-10 22:04:03 +02:00
|
|
|
|
2018-06-20 21:15:33 +02:00
|
|
|
UIStackView *reminderStackView = [UIStackView new];
|
2018-07-31 02:31:08 +02:00
|
|
|
_reminderStackView = reminderStackView;
|
2018-06-20 21:15:33 +02:00
|
|
|
reminderStackView.axis = UILayoutConstraintAxisVertical;
|
|
|
|
reminderStackView.spacing = 0;
|
2018-07-31 02:31:08 +02:00
|
|
|
_reminderViewCell = [UITableViewCell new];
|
|
|
|
self.reminderViewCell.selectionStyle = UITableViewCellSelectionStyleNone;
|
|
|
|
[self.reminderViewCell.contentView addSubview:reminderStackView];
|
|
|
|
[reminderStackView autoPinEdgesToSuperviewEdges];
|
2018-07-04 01:52:43 +02:00
|
|
|
|
2018-06-18 16:54:06 +02:00
|
|
|
__weak HomeViewController *weakSelf = self;
|
2018-06-15 19:39:35 +02:00
|
|
|
ReminderView *deregisteredView =
|
2018-06-18 16:54:06 +02:00
|
|
|
[ReminderView nagWithText:NSLocalizedString(@"DEREGISTRATION_WARNING",
|
2018-06-15 19:39:35 +02:00
|
|
|
@"Label warning the user that they have been de-registered.")
|
|
|
|
tapAction:^{
|
2018-06-18 17:28:21 +02:00
|
|
|
HomeViewController *strongSelf = weakSelf;
|
|
|
|
if (!strongSelf) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
[RegistrationUtils showReregistrationUIFromViewController:strongSelf];
|
2018-06-15 19:39:35 +02:00
|
|
|
}];
|
2018-06-22 19:42:59 +02:00
|
|
|
_deregisteredView = deregisteredView;
|
2018-06-20 21:15:33 +02:00
|
|
|
[reminderStackView addArrangedSubview:deregisteredView];
|
2018-06-15 19:39:35 +02:00
|
|
|
|
2018-06-19 18:09:44 +02:00
|
|
|
ReminderView *outageView = [ReminderView
|
|
|
|
nagWithText:NSLocalizedString(@"OUTAGE_WARNING", @"Label warning the user that the Signal service may be down.")
|
|
|
|
tapAction:nil];
|
2018-06-22 19:42:59 +02:00
|
|
|
_outageView = outageView;
|
2018-06-19 18:09:44 +02:00
|
|
|
[reminderStackView addArrangedSubview:outageView];
|
|
|
|
|
2018-04-23 19:49:11 +02:00
|
|
|
ReminderView *archiveReminderView =
|
|
|
|
[ReminderView explanationWithText:NSLocalizedString(@"INBOX_VIEW_ARCHIVE_MODE_REMINDER",
|
|
|
|
@"Label reminding the user that they are in archive mode.")];
|
2018-06-22 19:42:59 +02:00
|
|
|
_archiveReminderView = archiveReminderView;
|
2018-06-20 21:15:33 +02:00
|
|
|
[reminderStackView addArrangedSubview:archiveReminderView];
|
2017-07-10 22:38:18 +02:00
|
|
|
|
2018-04-23 19:49:11 +02:00
|
|
|
ReminderView *missingContactsPermissionView = [ReminderView
|
|
|
|
nagWithText:NSLocalizedString(@"INBOX_VIEW_MISSING_CONTACTS_PERMISSION",
|
|
|
|
@"Multi-line label explaining how to show names instead of phone numbers in your inbox")
|
|
|
|
tapAction:^{
|
|
|
|
[[UIApplication sharedApplication] openSystemSettings];
|
|
|
|
}];
|
2018-06-22 19:42:59 +02:00
|
|
|
_missingContactsPermissionView = missingContactsPermissionView;
|
2018-06-20 21:15:33 +02:00
|
|
|
[reminderStackView addArrangedSubview:missingContactsPermissionView];
|
2017-07-10 22:04:03 +02:00
|
|
|
|
|
|
|
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
|
|
|
self.tableView.delegate = self;
|
|
|
|
self.tableView.dataSource = self;
|
2018-04-10 22:35:49 +02:00
|
|
|
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
2018-08-22 22:30:12 +02:00
|
|
|
self.tableView.separatorColor = Theme.cellSeparatorColor;
|
2018-04-10 19:02:33 +02:00
|
|
|
[self.tableView registerClass:[HomeViewCell class] forCellReuseIdentifier:HomeViewCell.cellReuseIdentifier];
|
2018-04-23 18:33:23 +02:00
|
|
|
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kArchivedConversationsReuseIdentifier];
|
2017-07-10 22:04:03 +02:00
|
|
|
[self.view addSubview:self.tableView];
|
2018-07-31 02:31:08 +02:00
|
|
|
[self.tableView autoPinEdgesToSuperviewEdges];
|
2018-06-30 00:49:24 +02:00
|
|
|
|
2018-06-12 17:27:32 +02:00
|
|
|
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
2018-06-15 17:08:01 +02:00
|
|
|
self.tableView.estimatedRowHeight = 60;
|
2017-07-10 22:04:03 +02:00
|
|
|
|
|
|
|
UILabel *emptyBoxLabel = [UILabel new];
|
|
|
|
self.emptyBoxLabel = emptyBoxLabel;
|
|
|
|
[self.view addSubview:emptyBoxLabel];
|
2018-05-31 02:34:53 +02:00
|
|
|
|
|
|
|
// Let the label use as many lines as needed. It will very rarely be more than 2 but may happen for verbose locs.
|
|
|
|
[emptyBoxLabel setNumberOfLines:0];
|
2018-06-01 15:39:41 +02:00
|
|
|
emptyBoxLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
2018-05-31 02:34:53 +02:00
|
|
|
|
2018-05-20 06:35:06 +02:00
|
|
|
[emptyBoxLabel autoPinLeadingToSuperviewMargin];
|
|
|
|
[emptyBoxLabel autoPinTrailingToSuperviewMargin];
|
2018-06-01 02:33:14 +02:00
|
|
|
[emptyBoxLabel autoAlignAxisToSuperviewAxis:ALAxisHorizontal];
|
2017-07-10 22:04:03 +02:00
|
|
|
|
2017-10-14 17:41:54 +02:00
|
|
|
UIRefreshControl *pullToRefreshView = [UIRefreshControl new];
|
|
|
|
pullToRefreshView.tintColor = [UIColor grayColor];
|
|
|
|
[pullToRefreshView addTarget:self
|
|
|
|
action:@selector(pullToRefreshPerformed:)
|
|
|
|
forControlEvents:UIControlEventValueChanged];
|
|
|
|
[self.tableView insertSubview:pullToRefreshView atIndex:0];
|
2018-06-08 03:51:42 +02:00
|
|
|
|
2017-07-10 22:38:18 +02:00
|
|
|
[self updateReminderViews];
|
2017-07-10 22:04:03 +02:00
|
|
|
}
|
|
|
|
|
2017-07-10 22:38:18 +02:00
|
|
|
- (void)updateReminderViews
|
2017-07-10 22:04:03 +02:00
|
|
|
{
|
2018-06-22 19:42:59 +02:00
|
|
|
self.archiveReminderView.hidden = self.homeViewMode != HomeViewMode_Archive;
|
2018-07-16 21:51:00 +02:00
|
|
|
// App is killed and restarted when the user changes their contact permissions, so need need to "observe" anything
|
|
|
|
// to re-render this.
|
2018-06-22 19:50:24 +02:00
|
|
|
self.missingContactsPermissionView.hidden = !self.contactsManager.isSystemContactsDenied;
|
2018-06-22 19:42:59 +02:00
|
|
|
self.deregisteredView.hidden = !TSAccountManager.sharedInstance.isDeregistered;
|
|
|
|
self.outageView.hidden = !OutageDetection.sharedManager.hasOutage;
|
2018-07-31 02:31:08 +02:00
|
|
|
|
|
|
|
self.hasVisibleReminders = !self.archiveReminderView.isHidden || !self.missingContactsPermissionView.isHidden
|
|
|
|
|| !self.deregisteredView.isHidden || !self.outageView.isHidden;
|
2014-11-26 20:29:45 +01:00
|
|
|
}
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (void)viewDidLoad
|
|
|
|
{
|
2014-10-29 21:58:58 +01:00
|
|
|
[super viewDidLoad];
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2018-03-05 15:30:58 +01:00
|
|
|
self.editingDbConnection = OWSPrimaryStorage.sharedManager.newDatabaseConnection;
|
2018-06-08 03:51:42 +02:00
|
|
|
|
2017-05-31 23:49:21 +02:00
|
|
|
// Create the database connection.
|
|
|
|
[self uiDatabaseConnection];
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2018-04-23 18:42:58 +02:00
|
|
|
[self updateMappings];
|
|
|
|
[self checkIfEmptyView];
|
|
|
|
[self updateReminderViews];
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2017-07-18 21:18:52 +02:00
|
|
|
// because this uses the table data source, `tableViewSetup` must happen
|
|
|
|
// after mappings have been set up in `showInboxGrouping`
|
|
|
|
[self tableViewSetUp];
|
|
|
|
|
2018-04-23 18:42:58 +02:00
|
|
|
switch (self.homeViewMode) {
|
|
|
|
case HomeViewMode_Inbox:
|
|
|
|
// TODO: Should our app name be translated? Probably not.
|
|
|
|
self.title = NSLocalizedString(@"HOME_VIEW_TITLE_INBOX", @"Title for the home view's default mode.");
|
|
|
|
break;
|
|
|
|
case HomeViewMode_Archive:
|
|
|
|
self.title = NSLocalizedString(@"HOME_VIEW_TITLE_ARCHIVE", @"Title for the home view's 'archive' mode.");
|
|
|
|
break;
|
|
|
|
}
|
2018-04-25 18:52:45 +02:00
|
|
|
|
2018-04-26 17:50:33 +02:00
|
|
|
[self applyDefaultBackButton];
|
|
|
|
|
|
|
|
if ([self.traitCollection respondsToSelector:@selector(forceTouchCapability)]
|
|
|
|
&& (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable)) {
|
|
|
|
[self registerForPreviewingWithDelegate:self sourceView:self.tableView];
|
|
|
|
}
|
2018-06-08 03:51:42 +02:00
|
|
|
|
2018-06-09 12:52:03 +02:00
|
|
|
// Search
|
2018-06-11 18:15:46 +02:00
|
|
|
|
2018-08-21 22:56:21 +02:00
|
|
|
UISearchBar *searchBar = [OWSSearchBar new];
|
2018-06-11 18:15:46 +02:00
|
|
|
_searchBar = searchBar;
|
|
|
|
searchBar.placeholder = NSLocalizedString(@"HOME_VIEW_CONVERSATION_SEARCHBAR_PLACEHOLDER",
|
|
|
|
@"Placeholder text for search bar which filters conversations.");
|
|
|
|
searchBar.delegate = self;
|
|
|
|
[searchBar sizeToFit];
|
|
|
|
|
2018-06-08 03:51:42 +02:00
|
|
|
// Setting tableHeader calls numberOfSections, which must happen after updateMappings has been called at least once.
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(self.tableView.tableHeaderView == nil);
|
2018-06-11 18:15:46 +02:00
|
|
|
self.tableView.tableHeaderView = self.searchBar;
|
2018-08-16 17:42:31 +02:00
|
|
|
// Hide search bar by default. User can pull down to search.
|
|
|
|
self.tableView.contentOffset = CGPointMake(0, CGRectGetHeight(searchBar.frame));
|
2018-06-11 18:15:46 +02:00
|
|
|
|
2018-06-09 12:52:03 +02:00
|
|
|
ConversationSearchViewController *searchResultsController = [ConversationSearchViewController new];
|
2018-06-21 15:59:01 +02:00
|
|
|
searchResultsController.delegate = self;
|
2018-06-09 12:52:03 +02:00
|
|
|
self.searchResultsController = searchResultsController;
|
2018-06-11 18:15:46 +02:00
|
|
|
[self addChildViewController:searchResultsController];
|
|
|
|
[self.view addSubview:searchResultsController.view];
|
2018-07-31 02:31:08 +02:00
|
|
|
[searchResultsController.view autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero excludingEdge:ALEdgeTop];
|
2018-07-31 22:28:41 +02:00
|
|
|
if (@available(iOS 11, *)) {
|
|
|
|
[searchResultsController.view autoPinTopToSuperviewMarginWithInset:56];
|
|
|
|
} else {
|
|
|
|
[searchResultsController.view autoPinToTopLayoutGuideOfViewController:self withInset:40];
|
|
|
|
}
|
2018-06-13 19:35:55 +02:00
|
|
|
searchResultsController.view.hidden = YES;
|
2018-06-11 18:15:46 +02:00
|
|
|
|
2018-04-26 17:50:33 +02:00
|
|
|
[self updateBarButtonItems];
|
2018-07-23 21:03:07 +02:00
|
|
|
|
|
|
|
[self applyTheme];
|
2018-04-26 17:50:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)applyDefaultBackButton
|
|
|
|
{
|
2018-04-25 18:52:45 +02:00
|
|
|
// We don't show any text for the back button, so there's no need to localize it. But because we left align the
|
2018-04-25 22:50:07 +02:00
|
|
|
// conversation title view, we add a little tappable padding after the back button, by having a title of spaces.
|
2018-04-25 18:52:45 +02:00
|
|
|
// Admittedly this is kind of a hack and not super fine grained, but it's simple and results in the interactive pop
|
2018-04-25 22:50:07 +02:00
|
|
|
// gesture animating our title view nicely vs. creating our own back button bar item with custom padding, which does
|
|
|
|
// not properly animate with the "swipe to go back" or "swipe left for info" gestures.
|
2018-04-26 13:46:14 +02:00
|
|
|
NSUInteger paddingLength = 3;
|
|
|
|
NSString *paddingString = [@"" stringByPaddingToLength:paddingLength withString:@" " startingAtIndex:0];
|
|
|
|
|
2018-04-24 17:42:04 +02:00
|
|
|
self.navigationItem.backBarButtonItem =
|
2018-04-26 13:46:14 +02:00
|
|
|
[[UIBarButtonItem alloc] initWithTitle:paddingString style:UIBarButtonItemStylePlain target:nil action:nil];
|
2018-04-26 17:50:33 +02:00
|
|
|
}
|
2015-12-26 17:27:27 +01:00
|
|
|
|
2018-04-26 17:50:33 +02:00
|
|
|
- (void)applyArchiveBackButton
|
|
|
|
{
|
|
|
|
self.navigationItem.backBarButtonItem =
|
|
|
|
[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"BACK_BUTTON", @"button text for back button")
|
|
|
|
style:UIBarButtonItemStylePlain
|
|
|
|
target:nil
|
|
|
|
action:nil];
|
2017-02-17 18:00:10 +01:00
|
|
|
}
|
|
|
|
|
2018-03-06 18:27:04 +01:00
|
|
|
- (void)viewDidAppear:(BOOL)animated
|
|
|
|
{
|
|
|
|
[super viewDidAppear:animated];
|
|
|
|
|
2018-03-06 19:53:25 +01:00
|
|
|
[self displayAnyUnseenUpgradeExperience];
|
2018-04-26 17:50:33 +02:00
|
|
|
[self applyDefaultBackButton];
|
2018-07-23 21:03:07 +02:00
|
|
|
|
|
|
|
if (self.hasThemeChanged) {
|
|
|
|
[self.tableView reloadData];
|
|
|
|
self.hasThemeChanged = NO;
|
|
|
|
}
|
|
|
|
|
2018-08-07 23:36:34 +02:00
|
|
|
[self requestReviewIfAppropriate];
|
|
|
|
|
2018-07-23 21:03:07 +02:00
|
|
|
[self.searchResultsController viewDidAppear:animated];
|
2018-08-07 23:36:34 +02:00
|
|
|
|
|
|
|
self.hasEverAppeared = YES;
|
2018-07-23 21:03:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)viewDidDisappear:(BOOL)animated
|
|
|
|
{
|
|
|
|
[super viewDidDisappear:animated];
|
|
|
|
|
|
|
|
[self.searchResultsController viewDidDisappear:animated];
|
2018-03-06 18:27:04 +01:00
|
|
|
}
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (void)updateBarButtonItems
|
|
|
|
{
|
2018-04-23 18:42:58 +02:00
|
|
|
if (self.homeViewMode != HomeViewMode_Inbox) {
|
|
|
|
return;
|
|
|
|
}
|
2018-06-13 00:48:39 +02:00
|
|
|
|
|
|
|
// Settings button.
|
2018-07-13 15:50:49 +02:00
|
|
|
//
|
|
|
|
// TODO: Theme
|
2018-06-13 00:48:39 +02:00
|
|
|
UIImage *image = [UIImage imageNamed:@"button_settings_white"];
|
|
|
|
UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:@selector(settingsButtonPressed:)];
|
|
|
|
settingsButton.accessibilityLabel = CommonStrings.openSettingsButton;
|
2018-04-23 18:42:58 +02:00
|
|
|
self.navigationItem.leftBarButtonItem = settingsButton;
|
|
|
|
|
|
|
|
self.navigationItem.rightBarButtonItem =
|
|
|
|
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCompose
|
|
|
|
target:self
|
|
|
|
action:@selector(showNewConversationView)];
|
2017-02-17 18:00:10 +01:00
|
|
|
}
|
|
|
|
|
2018-04-10 19:02:33 +02:00
|
|
|
- (void)settingsButtonPressed:(id)sender
|
|
|
|
{
|
2017-09-07 17:03:54 +02:00
|
|
|
OWSNavigationController *navigationController = [AppSettingsViewController inModalNavigationController];
|
2017-05-23 16:25:47 +02:00
|
|
|
[self presentViewController:navigationController animated:YES completion:nil];
|
2015-10-31 16:53:32 +01:00
|
|
|
}
|
|
|
|
|
2018-06-12 17:27:32 +02:00
|
|
|
- (nullable UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext
|
|
|
|
viewControllerForLocation:(CGPoint)location
|
2017-09-06 19:59:39 +02:00
|
|
|
{
|
2015-12-22 12:45:09 +01:00
|
|
|
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
|
|
|
|
|
2018-07-31 02:31:08 +02:00
|
|
|
if (!indexPath) {
|
2018-05-14 17:47:13 +02:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2018-07-31 02:31:08 +02:00
|
|
|
if (indexPath.section != HomeViewControllerSectionConversations) {
|
2015-12-26 17:27:27 +01:00
|
|
|
return nil;
|
|
|
|
}
|
2018-07-31 02:31:08 +02:00
|
|
|
|
|
|
|
[previewingContext setSourceRect:[self.tableView rectForRowAtIndexPath:indexPath]];
|
|
|
|
|
|
|
|
ConversationViewController *vc = [ConversationViewController new];
|
|
|
|
TSThread *thread = [self threadForIndexPath:indexPath];
|
|
|
|
self.lastThread = thread;
|
|
|
|
[vc configureForThread:thread action:ConversationViewActionNone focusMessageId:nil];
|
|
|
|
[vc peekSetup];
|
|
|
|
|
|
|
|
return vc;
|
2015-10-31 16:53:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext
|
2017-09-06 19:59:39 +02:00
|
|
|
commitViewController:(UIViewController *)viewControllerToCommit
|
|
|
|
{
|
2017-09-06 20:13:18 +02:00
|
|
|
ConversationViewController *vc = (ConversationViewController *)viewControllerToCommit;
|
2015-10-31 16:53:32 +01:00
|
|
|
[vc popped];
|
2015-12-22 12:45:09 +01:00
|
|
|
|
|
|
|
[self.navigationController pushViewController:vc animated:NO];
|
2015-10-31 16:53:32 +01:00
|
|
|
}
|
|
|
|
|
2017-09-11 18:49:14 +02:00
|
|
|
- (void)showNewConversationView
|
2017-05-02 00:06:53 +02:00
|
|
|
{
|
2018-04-23 20:13:55 +02:00
|
|
|
OWSAssertIsOnMainThread();
|
|
|
|
|
2018-08-27 18:09:39 +02:00
|
|
|
OWSLogInfo(@"");
|
2018-04-23 20:13:55 +02:00
|
|
|
|
2017-09-06 19:55:01 +02:00
|
|
|
NewContactThreadViewController *viewController = [NewContactThreadViewController new];
|
2017-05-08 22:28:01 +02:00
|
|
|
|
|
|
|
[self.contactsManager requestSystemContactsOnceWithCompletion:^(NSError *_Nullable error) {
|
2017-05-11 22:09:39 +02:00
|
|
|
if (error) {
|
2018-08-27 18:51:32 +02:00
|
|
|
OWSLogError(@"Error when requesting contacts: %@", error);
|
2017-05-11 22:09:39 +02:00
|
|
|
}
|
|
|
|
// Even if there is an error fetching contacts we proceed to the next screen.
|
2017-05-08 22:28:01 +02:00
|
|
|
// As the compose view will present the proper thing depending on contact access.
|
|
|
|
//
|
|
|
|
// We just want to make sure contact access is *complete* before showing the compose
|
|
|
|
// screen to avoid flicker.
|
Faster conversation presentation.
There are multiple places in the codebase we present a conversation.
We used to have some very conservative machinery around how this was done, for
fear of failing to present the call view controller, which would have left a
hidden call in the background. We've since addressed that concern more
thoroughly via the separate calling UIWindow.
As such, the remaining presentation machinery is overly complex and inflexible
for what we need.
Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members)
Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation)
Sometimes we want to present the conversation with no animation (becoming active from a notification)
We also want to ensure that we're never pushing more than one conversation view
controller, which was previously a problem since we were "pushing" a newly
constructed VC in response to these myriad actions. It turned out there were
certain code paths that caused multiple actions to be fired in rapid succession
which pushed multiple ConversationVC's.
The built-in method: `setViewControllers:animated` easily ensures we only have
one ConversationVC on the stack, while being composable enough to faciliate the
various more efficient animations we desire.
The only thing lost with the complex methods is that the naive
`presentViewController:` can fail, e.g. if another view is already presented.
E.g. if an alert appears *just* before the user taps compose, the contact
picker will fail to present.
Since we no longer depend on this for presenting the CallViewController, this
isn't catostrophic, and in fact, arguable preferable, since we want the user to
read and dismiss any alert explicitly.
// FREEBIE
2018-08-18 22:54:35 +02:00
|
|
|
OWSNavigationController *modal = [[OWSNavigationController alloc] initWithRootViewController:viewController];
|
|
|
|
[self.navigationController presentViewController:modal animated:YES completion:nil];
|
2017-05-08 22:28:01 +02:00
|
|
|
}];
|
2015-10-31 23:13:28 +01:00
|
|
|
}
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (void)viewWillAppear:(BOOL)animated
|
|
|
|
{
|
2014-11-24 21:51:43 +01:00
|
|
|
[super viewWillAppear:animated];
|
2018-04-26 17:50:33 +02:00
|
|
|
|
2018-04-21 16:25:13 +02:00
|
|
|
__block BOOL hasAnyMessages;
|
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
|
|
|
|
hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction];
|
|
|
|
}];
|
|
|
|
if (hasAnyMessages) {
|
2017-06-30 06:26:37 +02:00
|
|
|
[self.contactsManager requestSystemContactsOnceWithCompletion:^(NSError *_Nullable error) {
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
2017-07-10 22:38:18 +02:00
|
|
|
[self updateReminderViews];
|
2017-06-30 06:26:37 +02:00
|
|
|
});
|
|
|
|
}];
|
2017-05-01 20:28:37 +02:00
|
|
|
}
|
2017-06-30 06:26:37 +02:00
|
|
|
|
2017-05-31 23:49:21 +02:00
|
|
|
self.isViewVisible = YES;
|
2017-07-10 22:04:03 +02:00
|
|
|
|
2018-06-26 22:40:53 +02:00
|
|
|
BOOL isShowingSearchResults = !self.searchResultsController.view.hidden;
|
|
|
|
if (isShowingSearchResults) {
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(self.searchBar.text.ows_stripped.length > 0);
|
2018-07-31 02:31:08 +02:00
|
|
|
[self scrollSearchBarToTopAnimated:NO];
|
2018-06-26 22:40:53 +02:00
|
|
|
} else if (self.lastThread) {
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(self.searchBar.text.ows_stripped.length == 0);
|
2018-06-26 22:40:53 +02:00
|
|
|
|
|
|
|
// When returning to home view, try to ensure that the "last" thread is still
|
|
|
|
// visible. The threads often change ordering while in conversation view due
|
|
|
|
// to incoming & outgoing messages.
|
2017-07-11 22:11:56 +02:00
|
|
|
__block NSIndexPath *indexPathOfLastThread = nil;
|
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
|
|
indexPathOfLastThread =
|
2018-06-26 22:40:53 +02:00
|
|
|
[[transaction extension:TSThreadDatabaseViewExtensionName] indexPathForKey:self.lastThread.uniqueId
|
|
|
|
inCollection:[TSThread collection]
|
|
|
|
withMappings:self.threadMappings];
|
2017-07-11 22:11:56 +02:00
|
|
|
}];
|
2018-06-26 22:40:53 +02:00
|
|
|
|
2017-07-10 22:04:03 +02:00
|
|
|
if (indexPathOfLastThread) {
|
|
|
|
[self.tableView scrollToRowAtIndexPath:indexPathOfLastThread
|
|
|
|
atScrollPosition:UITableViewScrollPositionNone
|
|
|
|
animated:NO];
|
|
|
|
}
|
|
|
|
}
|
2017-07-14 19:20:00 +02:00
|
|
|
|
|
|
|
[self checkIfEmptyView];
|
2018-04-26 17:50:33 +02:00
|
|
|
[self applyDefaultBackButton];
|
2018-04-26 21:56:56 +02:00
|
|
|
if ([self updateHasArchivedThreadsRow]) {
|
|
|
|
[self.tableView reloadData];
|
|
|
|
}
|
2018-07-23 21:03:07 +02:00
|
|
|
|
|
|
|
[self.searchResultsController viewWillAppear:animated];
|
2017-05-31 23:49:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated
|
|
|
|
{
|
|
|
|
[super viewWillDisappear:animated];
|
|
|
|
|
|
|
|
self.isViewVisible = NO;
|
2018-07-23 21:03:07 +02:00
|
|
|
|
|
|
|
[self.searchResultsController viewWillDisappear:animated];
|
2017-05-31 23:49:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setIsViewVisible:(BOOL)isViewVisible
|
|
|
|
{
|
|
|
|
_isViewVisible = isViewVisible;
|
|
|
|
|
|
|
|
[self updateShouldObserveDBModifications];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateShouldObserveDBModifications
|
|
|
|
{
|
2018-05-31 17:27:07 +02:00
|
|
|
BOOL isAppForegroundAndActive = CurrentAppContext().isAppForegroundAndActive;
|
2018-05-31 17:11:01 +02:00
|
|
|
self.shouldObserveDBModifications = self.isViewVisible && isAppForegroundAndActive;
|
2017-05-31 23:49:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setShouldObserveDBModifications:(BOOL)shouldObserveDBModifications
|
|
|
|
{
|
2017-07-14 19:43:36 +02:00
|
|
|
if (_shouldObserveDBModifications == shouldObserveDBModifications) {
|
|
|
|
return;
|
|
|
|
}
|
2017-07-14 19:34:24 +02:00
|
|
|
|
2017-07-14 19:43:36 +02:00
|
|
|
_shouldObserveDBModifications = shouldObserveDBModifications;
|
2017-05-31 23:49:21 +02:00
|
|
|
|
2017-07-26 18:39:43 +02:00
|
|
|
if (self.shouldObserveDBModifications) {
|
|
|
|
[self resetMappings];
|
2017-05-31 23:49:21 +02:00
|
|
|
}
|
2017-07-26 18:39:43 +02:00
|
|
|
}
|
2017-05-31 23:49:21 +02:00
|
|
|
|
2018-04-21 21:22:40 +02:00
|
|
|
- (void)reloadTableViewData
|
|
|
|
{
|
|
|
|
// PERF: come up with a more nuanced cache clearing scheme
|
2018-04-23 18:15:21 +02:00
|
|
|
[self.threadViewModelCache removeAllObjects];
|
2018-04-21 21:22:40 +02:00
|
|
|
[self.tableView reloadData];
|
|
|
|
}
|
|
|
|
|
2017-07-26 18:39:43 +02:00
|
|
|
- (void)resetMappings
|
|
|
|
{
|
2017-07-14 19:43:36 +02:00
|
|
|
// If we're entering "active" mode (e.g. view is visible and app is in foreground),
|
|
|
|
// reset all state updated by yapDatabaseModified:.
|
|
|
|
if (self.threadMappings != nil) {
|
|
|
|
// Before we begin observing database modifications, make sure
|
|
|
|
// our mapping and table state is up-to-date.
|
|
|
|
//
|
|
|
|
// We need to `beginLongLivedReadTransaction` before we update our
|
|
|
|
// mapping in order to jump to the most recent commit.
|
|
|
|
[self.uiDatabaseConnection beginLongLivedReadTransaction];
|
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
|
|
[self.threadMappings updateWithTransaction:transaction];
|
|
|
|
}];
|
2017-05-31 23:49:21 +02:00
|
|
|
}
|
2017-07-14 19:20:00 +02:00
|
|
|
|
2018-04-26 21:56:56 +02:00
|
|
|
[self updateHasArchivedThreadsRow];
|
2018-04-21 21:22:40 +02:00
|
|
|
[self reloadTableViewData];
|
|
|
|
|
2017-07-14 19:20:00 +02:00
|
|
|
[self checkIfEmptyView];
|
2017-07-26 18:39:43 +02:00
|
|
|
|
|
|
|
// If the user hasn't already granted contact access
|
|
|
|
// we don't want to request until they receive a message.
|
2018-04-21 16:25:13 +02:00
|
|
|
__block BOOL hasAnyMessages;
|
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
|
|
|
|
hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction];
|
|
|
|
}];
|
|
|
|
if (hasAnyMessages) {
|
2017-07-26 18:39:43 +02:00
|
|
|
[self.contactsManager requestSystemContactsOnce];
|
|
|
|
}
|
2017-05-31 23:49:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)applicationWillEnterForeground:(NSNotification *)notification
|
|
|
|
{
|
2017-07-14 19:43:36 +02:00
|
|
|
[self checkIfEmptyView];
|
2017-05-31 23:49:21 +02:00
|
|
|
}
|
|
|
|
|
2018-04-21 16:25:13 +02:00
|
|
|
- (BOOL)hasAnyMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction
|
|
|
|
{
|
|
|
|
return [TSThread numberOfKeysInCollectionWithTransaction:transaction] > 0;
|
|
|
|
}
|
|
|
|
|
2017-11-17 19:46:14 +01:00
|
|
|
- (void)applicationDidBecomeActive:(NSNotification *)notification
|
|
|
|
{
|
2018-05-31 17:11:01 +02:00
|
|
|
[self updateShouldObserveDBModifications];
|
|
|
|
|
2017-11-17 19:46:14 +01:00
|
|
|
// It's possible a thread was created while we where in the background. But since we don't honor contact
|
|
|
|
// requests unless the app is in the foregrond, we must check again here upon becoming active.
|
2018-04-21 16:25:13 +02:00
|
|
|
__block BOOL hasAnyMessages;
|
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
|
|
|
|
hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction];
|
|
|
|
}];
|
|
|
|
|
|
|
|
if (hasAnyMessages) {
|
2017-11-17 19:46:14 +01:00
|
|
|
[self.contactsManager requestSystemContactsOnceWithCompletion:^(NSError *_Nullable error) {
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
[self updateReminderViews];
|
|
|
|
});
|
|
|
|
}];
|
|
|
|
}
|
2016-10-10 22:02:09 +02:00
|
|
|
}
|
|
|
|
|
2018-05-31 17:11:01 +02:00
|
|
|
- (void)applicationWillResignActive:(NSNotification *)notification
|
|
|
|
{
|
|
|
|
[self updateShouldObserveDBModifications];
|
|
|
|
}
|
|
|
|
|
2017-02-27 17:04:14 +01:00
|
|
|
#pragma mark - startup
|
|
|
|
|
2017-05-26 00:00:41 +02:00
|
|
|
- (NSArray<ExperienceUpgrade *> *)unseenUpgradeExperiences
|
2017-02-27 17:04:14 +01:00
|
|
|
{
|
2017-12-19 17:38:25 +01:00
|
|
|
OWSAssertIsOnMainThread();
|
2017-02-27 17:04:14 +01:00
|
|
|
|
|
|
|
__block NSArray<ExperienceUpgrade *> *unseenUpgrades;
|
2018-03-06 19:53:25 +01:00
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
2017-12-07 16:33:27 +01:00
|
|
|
unseenUpgrades = [ExperienceUpgradeFinder.sharedManager allUnseenWithTransaction:transaction];
|
2017-02-27 17:04:14 +01:00
|
|
|
}];
|
2017-05-26 00:00:41 +02:00
|
|
|
return unseenUpgrades;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)displayAnyUnseenUpgradeExperience
|
|
|
|
{
|
2017-12-19 17:38:25 +01:00
|
|
|
OWSAssertIsOnMainThread();
|
2017-05-26 00:00:41 +02:00
|
|
|
|
|
|
|
NSArray<ExperienceUpgrade *> *unseenUpgrades = [self unseenUpgradeExperiences];
|
2017-02-27 17:04:14 +01:00
|
|
|
|
|
|
|
if (unseenUpgrades.count > 0) {
|
2017-09-06 19:59:39 +02:00
|
|
|
ExperienceUpgradesPageViewController *experienceUpgradeViewController =
|
|
|
|
[[ExperienceUpgradesPageViewController alloc] initWithExperienceUpgrades:unseenUpgrades];
|
2018-03-06 19:53:25 +01:00
|
|
|
[self presentViewController:experienceUpgradeViewController animated:YES completion:nil];
|
2018-02-13 21:00:25 +01:00
|
|
|
} else {
|
|
|
|
[OWSAlerts showIOSUpgradeNagIfNecessary];
|
2017-02-27 17:04:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (void)tableViewSetUp
|
|
|
|
{
|
2014-11-21 14:38:37 +01:00
|
|
|
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
|
2014-10-29 21:58:58 +01:00
|
|
|
}
|
|
|
|
|
2015-01-24 04:26:04 +01:00
|
|
|
#pragma mark - Table View Data Source
|
2014-10-29 21:58:58 +01:00
|
|
|
|
2018-04-26 21:56:56 +02:00
|
|
|
// Returns YES IFF this value changes.
|
|
|
|
- (BOOL)updateHasArchivedThreadsRow
|
|
|
|
{
|
|
|
|
BOOL hasArchivedThreadsRow = (self.homeViewMode == HomeViewMode_Inbox && self.numberOfArchivedThreads > 0);
|
|
|
|
if (self.hasArchivedThreadsRow == hasArchivedThreadsRow) {
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
self.hasArchivedThreadsRow = hasArchivedThreadsRow;
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2017-05-31 23:49:21 +02:00
|
|
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
|
|
|
{
|
2014-11-21 14:38:37 +01:00
|
|
|
return (NSInteger)[self.threadMappings numberOfSections];
|
2014-10-29 21:58:58 +01:00
|
|
|
}
|
|
|
|
|
2018-07-31 02:31:08 +02:00
|
|
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)aSection
|
2017-09-06 19:59:39 +02:00
|
|
|
{
|
2018-07-31 02:31:08 +02:00
|
|
|
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;
|
|
|
|
}
|
2018-04-23 18:33:23 +02:00
|
|
|
}
|
|
|
|
|
2018-08-27 18:55:37 +02:00
|
|
|
OWSFailDebug(@"failure: unexpected section: %lu", (unsigned long)section);
|
2018-07-31 02:31:08 +02:00
|
|
|
return 0;
|
2014-10-29 21:58:58 +01:00
|
|
|
}
|
|
|
|
|
2018-04-23 18:15:21 +02:00
|
|
|
- (ThreadViewModel *)threadViewModelForIndexPath:(NSIndexPath *)indexPath
|
2017-05-05 18:39:21 +02:00
|
|
|
{
|
2018-04-21 17:12:58 +02:00
|
|
|
TSThread *threadRecord = [self threadForIndexPath:indexPath];
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(threadRecord);
|
2018-04-21 19:00:58 +02:00
|
|
|
|
2018-04-23 18:15:21 +02:00
|
|
|
ThreadViewModel *_Nullable cachedThreadViewModel = [self.threadViewModelCache objectForKey:threadRecord.uniqueId];
|
|
|
|
if (cachedThreadViewModel) {
|
|
|
|
return cachedThreadViewModel;
|
2018-04-21 19:00:58 +02:00
|
|
|
}
|
|
|
|
|
2018-04-23 18:15:21 +02:00
|
|
|
__block ThreadViewModel *_Nullable newThreadViewModel;
|
2018-04-21 17:12:58 +02:00
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
|
2018-04-23 18:15:21 +02:00
|
|
|
newThreadViewModel = [[ThreadViewModel alloc] initWithThread:threadRecord transaction:transaction];
|
2018-04-21 16:25:13 +02:00
|
|
|
}];
|
2018-04-23 18:15:21 +02:00
|
|
|
[self.threadViewModelCache setObject:newThreadViewModel forKey:threadRecord.uniqueId];
|
|
|
|
return newThreadViewModel;
|
2018-04-21 19:00:58 +02:00
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2018-04-21 19:00:58 +02:00
|
|
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
2018-04-23 18:33:23 +02:00
|
|
|
{
|
2018-07-31 02:31:08 +02:00
|
|
|
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];
|
|
|
|
}
|
2018-04-23 18:33:23 +02:00
|
|
|
}
|
2018-07-31 02:31:08 +02:00
|
|
|
|
2018-08-27 18:55:37 +02:00
|
|
|
OWSFailDebug(@"failure: unexpected section: %lu", (unsigned long)section);
|
2018-07-31 02:31:08 +02:00
|
|
|
return [UITableViewCell new];
|
2018-04-23 18:33:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForConversationAtIndexPath:(NSIndexPath *)indexPath
|
2018-04-21 19:00:58 +02:00
|
|
|
{
|
|
|
|
HomeViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:HomeViewCell.cellReuseIdentifier];
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(cell);
|
2018-04-21 19:00:58 +02:00
|
|
|
|
2018-04-23 18:15:21 +02:00
|
|
|
ThreadViewModel *thread = [self threadViewModelForIndexPath:indexPath];
|
2018-04-21 17:12:58 +02:00
|
|
|
[cell configureWithThread:thread
|
|
|
|
contactsManager:self.contactsManager
|
|
|
|
blockedPhoneNumberSet:self.blockedPhoneNumberSet];
|
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
return cell;
|
2014-11-21 14:38:37 +01:00
|
|
|
}
|
2014-10-29 21:58:58 +01:00
|
|
|
|
2018-04-23 18:33:23 +02:00
|
|
|
- (UITableViewCell *)cellForArchivedConversationsRow:(UITableView *)tableView
|
|
|
|
{
|
|
|
|
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kArchivedConversationsReuseIdentifier];
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(cell);
|
2018-07-13 15:59:33 +02:00
|
|
|
[OWSTableItem configureCell:cell];
|
2018-04-23 18:33:23 +02:00
|
|
|
|
|
|
|
for (UIView *subview in cell.contentView.subviews) {
|
|
|
|
[subview removeFromSuperview];
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:00:22 +02:00
|
|
|
UIImage *disclosureImage = [UIImage imageNamed:(CurrentAppContext().isRTL ? @"NavBarBack" : @"NavBarBackRTL")];
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(disclosureImage);
|
2018-04-23 18:33:23 +02:00
|
|
|
UIImageView *disclosureImageView = [UIImageView new];
|
|
|
|
disclosureImageView.image = [disclosureImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
|
|
|
disclosureImageView.tintColor = [UIColor colorWithRGBHex:0xd1d1d6];
|
|
|
|
[disclosureImageView setContentHuggingHigh];
|
|
|
|
[disclosureImageView setCompressionResistanceHigh];
|
|
|
|
|
|
|
|
UILabel *label = [UILabel new];
|
|
|
|
label.text = NSLocalizedString(@"HOME_VIEW_ARCHIVED_CONVERSATIONS", @"Label for 'archived conversations' button.");
|
|
|
|
label.textAlignment = NSTextAlignmentCenter;
|
|
|
|
label.font = [UIFont ows_dynamicTypeBodyFont];
|
2018-07-13 15:50:49 +02:00
|
|
|
label.textColor = Theme.primaryColor;
|
2018-04-23 18:33:23 +02:00
|
|
|
|
|
|
|
UIStackView *stackView = [UIStackView new];
|
|
|
|
stackView.axis = UILayoutConstraintAxisHorizontal;
|
|
|
|
stackView.spacing = 5;
|
2018-04-25 19:29:10 +02:00
|
|
|
// If alignment isn't set, UIStackView uses the height of
|
|
|
|
// disclosureImageView, even if label has a higher desired height.
|
|
|
|
stackView.alignment = UIStackViewAlignmentCenter;
|
2018-04-24 17:42:04 +02:00
|
|
|
[stackView addArrangedSubview:label];
|
|
|
|
[stackView addArrangedSubview:disclosureImageView];
|
2018-04-23 18:33:23 +02:00
|
|
|
[cell.contentView addSubview:stackView];
|
|
|
|
[stackView autoCenterInSuperview];
|
|
|
|
// Constrain to cell margins.
|
|
|
|
[stackView autoPinEdgeToSuperviewMargin:ALEdgeLeading relation:NSLayoutRelationGreaterThanOrEqual];
|
|
|
|
[stackView autoPinEdgeToSuperviewMargin:ALEdgeTrailing relation:NSLayoutRelationGreaterThanOrEqual];
|
2018-06-20 21:15:33 +02:00
|
|
|
[stackView autoPinEdgeToSuperviewMargin:ALEdgeTop];
|
|
|
|
[stackView autoPinEdgeToSuperviewMargin:ALEdgeBottom];
|
2018-04-23 18:33:23 +02:00
|
|
|
|
|
|
|
return cell;
|
|
|
|
}
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (TSThread *)threadForIndexPath:(NSIndexPath *)indexPath
|
|
|
|
{
|
2014-11-21 14:38:37 +01:00
|
|
|
__block TSThread *thread = nil;
|
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
2017-09-06 19:59:39 +02:00
|
|
|
thread = [[transaction extension:TSThreadDatabaseViewExtensionName] objectAtIndexPath:indexPath
|
|
|
|
withMappings:self.threadMappings];
|
2014-11-21 14:38:37 +01:00
|
|
|
}];
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2014-11-21 14:38:37 +01:00
|
|
|
return thread;
|
2014-10-29 21:58:58 +01:00
|
|
|
}
|
|
|
|
|
2017-10-14 17:41:54 +02:00
|
|
|
- (void)pullToRefreshPerformed:(UIRefreshControl *)refreshControl
|
|
|
|
{
|
2017-12-19 17:38:25 +01:00
|
|
|
OWSAssertIsOnMainThread();
|
2018-08-27 18:51:32 +02:00
|
|
|
OWSLogInfo(@"beggining refreshing.");
|
2017-12-04 16:35:47 +01:00
|
|
|
[SignalApp.sharedApp.messageFetcherJob run].always(^{
|
2018-08-27 18:51:32 +02:00
|
|
|
OWSLogInfo(@"ending refreshing.");
|
2017-10-14 17:41:54 +02:00
|
|
|
[refreshControl endRefreshing];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
#pragma mark Table Swipe to Delete
|
|
|
|
|
2015-12-22 12:45:09 +01:00
|
|
|
- (void)tableView:(UITableView *)tableView
|
|
|
|
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
|
2017-09-06 19:59:39 +02:00
|
|
|
forRowAtIndexPath:(NSIndexPath *)indexPath
|
|
|
|
{
|
2015-01-14 22:30:01 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-06-12 17:27:32 +02:00
|
|
|
- (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
|
2017-09-06 19:59:39 +02:00
|
|
|
{
|
2018-07-31 02:31:08 +02:00
|
|
|
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];
|
|
|
|
}];
|
|
|
|
|
|
|
|
} 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 @[];
|
|
|
|
}
|
2015-12-26 17:27:27 +01:00
|
|
|
}
|
2015-01-14 22:30:01 +01:00
|
|
|
}
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
|
|
|
|
{
|
2018-07-31 02:31:08 +02:00
|
|
|
HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section;
|
|
|
|
switch (section) {
|
|
|
|
case HomeViewControllerSectionReminders: {
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
case HomeViewControllerSectionConversations: {
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
case HomeViewControllerSectionArchiveButton: {
|
|
|
|
return NO;
|
|
|
|
}
|
2018-05-14 17:47:13 +02:00
|
|
|
}
|
2015-01-14 22:30:01 +01:00
|
|
|
}
|
|
|
|
|
2018-08-21 22:56:21 +02:00
|
|
|
#pragma mark - UISearchBarDelegate
|
2018-06-11 18:15:46 +02:00
|
|
|
|
2018-08-21 22:56:21 +02:00
|
|
|
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
|
2018-06-11 18:15:46 +02:00
|
|
|
{
|
2018-08-21 22:56:21 +02:00
|
|
|
[self scrollSearchBarToTopAnimated:NO];
|
|
|
|
|
2018-06-11 18:15:46 +02:00
|
|
|
[self updateSearchResultsVisibility];
|
2018-08-21 22:56:21 +02:00
|
|
|
|
|
|
|
[self ensureSearchBarCancelButton];
|
2018-06-11 18:15:46 +02:00
|
|
|
}
|
2018-06-09 12:52:03 +02:00
|
|
|
|
2018-08-21 22:56:21 +02:00
|
|
|
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
|
2018-06-09 12:52:03 +02:00
|
|
|
{
|
2018-06-11 18:15:46 +02:00
|
|
|
[self updateSearchResultsVisibility];
|
2018-08-21 22:56:21 +02:00
|
|
|
|
|
|
|
[self ensureSearchBarCancelButton];
|
2018-06-11 18:15:46 +02:00
|
|
|
}
|
|
|
|
|
2018-08-21 22:56:21 +02:00
|
|
|
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
2018-06-11 18:15:46 +02:00
|
|
|
{
|
2018-08-21 22:56:21 +02:00
|
|
|
[self updateSearchResultsVisibility];
|
|
|
|
|
|
|
|
[self ensureSearchBarCancelButton];
|
|
|
|
}
|
2018-06-21 15:59:01 +02:00
|
|
|
|
2018-08-21 22:56:21 +02:00
|
|
|
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
|
|
|
|
{
|
2018-06-11 18:15:46 +02:00
|
|
|
[self updateSearchResultsVisibility];
|
|
|
|
}
|
|
|
|
|
2018-08-21 22:56:21 +02:00
|
|
|
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
|
|
|
|
{
|
|
|
|
self.searchBar.text = nil;
|
|
|
|
|
|
|
|
[self.searchBar resignFirstResponder];
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(!self.searchBar.isFirstResponder);
|
2018-08-21 22:56:21 +02:00
|
|
|
|
|
|
|
[self updateSearchResultsVisibility];
|
|
|
|
|
|
|
|
[self ensureSearchBarCancelButton];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)ensureSearchBarCancelButton
|
|
|
|
{
|
|
|
|
self.searchBar.showsCancelButton = (self.searchBar.isFirstResponder || self.searchBar.text.length > 0);
|
|
|
|
}
|
|
|
|
|
2018-06-11 18:15:46 +02:00
|
|
|
- (void)updateSearchResultsVisibility
|
|
|
|
{
|
|
|
|
OWSAssertIsOnMainThread();
|
|
|
|
|
2018-06-11 18:52:46 +02:00
|
|
|
NSString *searchText = self.searchBar.text.ows_stripped;
|
2018-06-13 17:37:01 +02:00
|
|
|
self.searchResultsController.searchText = searchText;
|
2018-06-11 18:52:46 +02:00
|
|
|
BOOL isSearching = searchText.length > 0;
|
2018-06-11 18:15:46 +02:00
|
|
|
self.searchResultsController.view.hidden = !isSearching;
|
2018-06-13 19:35:55 +02:00
|
|
|
|
|
|
|
if (isSearching) {
|
2018-07-31 02:31:08 +02:00
|
|
|
[self scrollSearchBarToTopAnimated:NO];
|
2018-06-13 19:35:55 +02:00
|
|
|
self.tableView.scrollEnabled = NO;
|
|
|
|
} else {
|
|
|
|
self.tableView.scrollEnabled = YES;
|
|
|
|
}
|
2018-06-09 12:52:03 +02:00
|
|
|
}
|
|
|
|
|
2018-07-31 02:31:08 +02:00
|
|
|
- (void)scrollSearchBarToTopAnimated:(BOOL)isAnimated
|
|
|
|
{
|
|
|
|
CGFloat topInset = self.topLayoutGuide.length;
|
|
|
|
[self.tableView setContentOffset:CGPointMake(0, -topInset) animated:isAnimated];
|
|
|
|
}
|
|
|
|
|
2018-06-21 15:59:01 +02:00
|
|
|
#pragma mark - UIScrollViewDelegate
|
|
|
|
|
|
|
|
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
|
|
{
|
|
|
|
[self.searchBar resignFirstResponder];
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(!self.searchBar.isFirstResponder);
|
2018-06-21 15:59:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - ConversationSearchViewDelegate
|
|
|
|
|
|
|
|
- (void)conversationSearchViewWillBeginDragging
|
|
|
|
{
|
|
|
|
[self.searchBar resignFirstResponder];
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(!self.searchBar.isFirstResponder);
|
2018-06-21 15:59:01 +02:00
|
|
|
}
|
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
#pragma mark - HomeFeedTableViewCellDelegate
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (void)tableViewCellTappedDelete:(NSIndexPath *)indexPath
|
|
|
|
{
|
2018-07-31 02:31:08 +02:00
|
|
|
if (indexPath.section != HomeViewControllerSectionConversations) {
|
2018-08-27 18:55:37 +02:00
|
|
|
OWSFailDebug(@"failure: unexpected section: %lu", (unsigned long)indexPath.section);
|
2018-07-31 02:31:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-12-22 12:45:09 +01:00
|
|
|
TSThread *thread = [self threadForIndexPath:indexPath];
|
|
|
|
if ([thread isKindOfClass:[TSGroupThread class]]) {
|
2016-11-15 11:15:51 +01:00
|
|
|
|
|
|
|
TSGroupThread *gThread = (TSGroupThread *)thread;
|
|
|
|
if ([gThread.groupModel.groupMemberIds containsObject:[TSAccountManager localNumber]]) {
|
|
|
|
UIAlertController *removingFromGroup = [UIAlertController
|
2017-09-06 19:59:39 +02:00
|
|
|
alertControllerWithTitle:[NSString
|
|
|
|
stringWithFormat:NSLocalizedString(@"GROUP_REMOVING", nil), [thread name]]
|
|
|
|
message:nil
|
|
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
2016-11-15 11:15:51 +01:00
|
|
|
[self presentViewController:removingFromGroup animated:YES completion:nil];
|
|
|
|
|
2018-06-13 22:44:22 +02:00
|
|
|
TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:thread
|
2018-08-31 18:43:05 +02:00
|
|
|
groupMetaMessage:TSGroupMetaMessageQuit
|
2018-06-13 22:44:22 +02:00
|
|
|
expiresInSeconds:0];
|
2017-11-15 19:21:31 +01:00
|
|
|
[self.messageSender enqueueMessage:message
|
2017-05-09 16:04:48 +02:00
|
|
|
success:^{
|
|
|
|
[self dismissViewControllerAnimated:YES
|
|
|
|
completion:^{
|
|
|
|
[self deleteThread:thread];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
failure:^(NSError *error) {
|
|
|
|
[self dismissViewControllerAnimated:YES
|
|
|
|
completion:^{
|
|
|
|
[OWSAlerts
|
|
|
|
showAlertWithTitle:
|
|
|
|
NSLocalizedString(@"GROUP_REMOVING_FAILED",
|
|
|
|
@"Title of alert indicating that group deletion failed.")
|
|
|
|
message:error.localizedRecoverySuggestion];
|
|
|
|
}];
|
|
|
|
}];
|
2016-11-15 11:15:51 +01:00
|
|
|
} else {
|
|
|
|
[self deleteThread:thread];
|
|
|
|
}
|
2015-12-26 17:27:27 +01:00
|
|
|
} else {
|
|
|
|
[self deleteThread:thread];
|
2014-12-24 02:25:10 +01:00
|
|
|
}
|
2015-12-26 17:27:27 +01:00
|
|
|
}
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (void)deleteThread:(TSThread *)thread
|
|
|
|
{
|
2014-12-05 23:38:13 +01:00
|
|
|
[self.editingDbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
2017-09-06 19:59:39 +02:00
|
|
|
[thread removeWithTransaction:transaction];
|
2014-12-05 23:38:13 +01:00
|
|
|
}];
|
2015-12-26 17:27:27 +01:00
|
|
|
|
2015-01-27 02:20:11 +01:00
|
|
|
[self checkIfEmptyView];
|
2014-10-29 21:58:58 +01:00
|
|
|
}
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (void)archiveIndexPath:(NSIndexPath *)indexPath
|
|
|
|
{
|
2018-07-31 02:31:08 +02:00
|
|
|
if (indexPath.section != HomeViewControllerSectionConversations) {
|
2018-08-27 18:55:37 +02:00
|
|
|
OWSFailDebug(@"failure: unexpected section: %lu", (unsigned long)indexPath.section);
|
2018-07-31 02:31:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-12-26 17:27:27 +01:00
|
|
|
TSThread *thread = [self threadForIndexPath:indexPath];
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2014-12-05 23:38:13 +01:00
|
|
|
[self.editingDbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
2018-04-23 18:33:23 +02:00
|
|
|
switch (self.homeViewMode) {
|
|
|
|
case HomeViewMode_Inbox:
|
|
|
|
[thread archiveThreadWithTransaction:transaction];
|
|
|
|
break;
|
|
|
|
case HomeViewMode_Archive:
|
|
|
|
[thread unarchiveThreadWithTransaction:transaction];
|
|
|
|
break;
|
|
|
|
}
|
2014-12-05 23:38:13 +01:00
|
|
|
}];
|
2015-01-27 02:20:11 +01:00
|
|
|
[self checkIfEmptyView];
|
2015-01-14 22:30:01 +01:00
|
|
|
}
|
|
|
|
|
2018-04-23 18:33:23 +02:00
|
|
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
2017-07-12 16:46:54 +02:00
|
|
|
{
|
2018-08-27 18:51:32 +02:00
|
|
|
OWSLogInfo(@"%ld %ld", (long)indexPath.row, (long)indexPath.section);
|
2018-06-22 20:40:48 +02:00
|
|
|
|
|
|
|
[self.searchBar resignFirstResponder];
|
2018-07-31 02:31:08 +02:00
|
|
|
HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section;
|
|
|
|
switch (section) {
|
|
|
|
case HomeViewControllerSectionReminders: {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case HomeViewControllerSectionConversations: {
|
|
|
|
TSThread *thread = [self threadForIndexPath:indexPath];
|
Faster conversation presentation.
There are multiple places in the codebase we present a conversation.
We used to have some very conservative machinery around how this was done, for
fear of failing to present the call view controller, which would have left a
hidden call in the background. We've since addressed that concern more
thoroughly via the separate calling UIWindow.
As such, the remaining presentation machinery is overly complex and inflexible
for what we need.
Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members)
Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation)
Sometimes we want to present the conversation with no animation (becoming active from a notification)
We also want to ensure that we're never pushing more than one conversation view
controller, which was previously a problem since we were "pushing" a newly
constructed VC in response to these myriad actions. It turned out there were
certain code paths that caused multiple actions to be fired in rapid succession
which pushed multiple ConversationVC's.
The built-in method: `setViewControllers:animated` easily ensures we only have
one ConversationVC on the stack, while being composable enough to faciliate the
various more efficient animations we desire.
The only thing lost with the complex methods is that the naive
`presentViewController:` can fail, e.g. if another view is already presented.
E.g. if an alert appears *just* before the user taps compose, the contact
picker will fail to present.
Since we no longer depend on this for presenting the CallViewController, this
isn't catostrophic, and in fact, arguable preferable, since we want the user to
read and dismiss any alert explicitly.
// FREEBIE
2018-08-18 22:54:35 +02:00
|
|
|
[self presentThread:thread action:ConversationViewActionNone animated:YES];
|
2018-07-31 02:31:08 +02:00
|
|
|
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case HomeViewControllerSectionArchiveButton: {
|
|
|
|
[self showArchivedConversations];
|
|
|
|
break;
|
|
|
|
}
|
2015-02-10 12:02:58 +01:00
|
|
|
}
|
2015-12-26 17:27:27 +01:00
|
|
|
}
|
|
|
|
|
Faster conversation presentation.
There are multiple places in the codebase we present a conversation.
We used to have some very conservative machinery around how this was done, for
fear of failing to present the call view controller, which would have left a
hidden call in the background. We've since addressed that concern more
thoroughly via the separate calling UIWindow.
As such, the remaining presentation machinery is overly complex and inflexible
for what we need.
Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members)
Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation)
Sometimes we want to present the conversation with no animation (becoming active from a notification)
We also want to ensure that we're never pushing more than one conversation view
controller, which was previously a problem since we were "pushing" a newly
constructed VC in response to these myriad actions. It turned out there were
certain code paths that caused multiple actions to be fired in rapid succession
which pushed multiple ConversationVC's.
The built-in method: `setViewControllers:animated` easily ensures we only have
one ConversationVC on the stack, while being composable enough to faciliate the
various more efficient animations we desire.
The only thing lost with the complex methods is that the naive
`presentViewController:` can fail, e.g. if another view is already presented.
E.g. if an alert appears *just* before the user taps compose, the contact
picker will fail to present.
Since we no longer depend on this for presenting the CallViewController, this
isn't catostrophic, and in fact, arguable preferable, since we want the user to
read and dismiss any alert explicitly.
// FREEBIE
2018-08-18 22:54:35 +02:00
|
|
|
- (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action animated:(BOOL)isAnimated
|
2018-06-11 21:31:54 +02:00
|
|
|
{
|
Faster conversation presentation.
There are multiple places in the codebase we present a conversation.
We used to have some very conservative machinery around how this was done, for
fear of failing to present the call view controller, which would have left a
hidden call in the background. We've since addressed that concern more
thoroughly via the separate calling UIWindow.
As such, the remaining presentation machinery is overly complex and inflexible
for what we need.
Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members)
Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation)
Sometimes we want to present the conversation with no animation (becoming active from a notification)
We also want to ensure that we're never pushing more than one conversation view
controller, which was previously a problem since we were "pushing" a newly
constructed VC in response to these myriad actions. It turned out there were
certain code paths that caused multiple actions to be fired in rapid succession
which pushed multiple ConversationVC's.
The built-in method: `setViewControllers:animated` easily ensures we only have
one ConversationVC on the stack, while being composable enough to faciliate the
various more efficient animations we desire.
The only thing lost with the complex methods is that the naive
`presentViewController:` can fail, e.g. if another view is already presented.
E.g. if an alert appears *just* before the user taps compose, the contact
picker will fail to present.
Since we no longer depend on this for presenting the CallViewController, this
isn't catostrophic, and in fact, arguable preferable, since we want the user to
read and dismiss any alert explicitly.
// FREEBIE
2018-08-18 22:54:35 +02:00
|
|
|
[self presentThread:thread action:action focusMessageId:nil animated:isAnimated];
|
2018-06-11 21:31:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)presentThread:(TSThread *)thread
|
|
|
|
action:(ConversationViewAction)action
|
|
|
|
focusMessageId:(nullable NSString *)focusMessageId
|
Faster conversation presentation.
There are multiple places in the codebase we present a conversation.
We used to have some very conservative machinery around how this was done, for
fear of failing to present the call view controller, which would have left a
hidden call in the background. We've since addressed that concern more
thoroughly via the separate calling UIWindow.
As such, the remaining presentation machinery is overly complex and inflexible
for what we need.
Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members)
Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation)
Sometimes we want to present the conversation with no animation (becoming active from a notification)
We also want to ensure that we're never pushing more than one conversation view
controller, which was previously a problem since we were "pushing" a newly
constructed VC in response to these myriad actions. It turned out there were
certain code paths that caused multiple actions to be fired in rapid succession
which pushed multiple ConversationVC's.
The built-in method: `setViewControllers:animated` easily ensures we only have
one ConversationVC on the stack, while being composable enough to faciliate the
various more efficient animations we desire.
The only thing lost with the complex methods is that the naive
`presentViewController:` can fail, e.g. if another view is already presented.
E.g. if an alert appears *just* before the user taps compose, the contact
picker will fail to present.
Since we no longer depend on this for presenting the CallViewController, this
isn't catostrophic, and in fact, arguable preferable, since we want the user to
read and dismiss any alert explicitly.
// FREEBIE
2018-08-18 22:54:35 +02:00
|
|
|
animated:(BOOL)isAnimated
|
2017-04-18 22:08:01 +02:00
|
|
|
{
|
2017-08-31 18:31:13 +02:00
|
|
|
if (thread == nil) {
|
2018-08-27 16:29:51 +02:00
|
|
|
OWSFailDebug(@"Thread unexpectedly nil");
|
2017-08-31 18:31:13 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-03 19:16:45 +02:00
|
|
|
// We do this synchronously if we're already on the main thread.
|
|
|
|
DispatchMainThreadSafe(^{
|
Faster conversation presentation.
There are multiple places in the codebase we present a conversation.
We used to have some very conservative machinery around how this was done, for
fear of failing to present the call view controller, which would have left a
hidden call in the background. We've since addressed that concern more
thoroughly via the separate calling UIWindow.
As such, the remaining presentation machinery is overly complex and inflexible
for what we need.
Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members)
Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation)
Sometimes we want to present the conversation with no animation (becoming active from a notification)
We also want to ensure that we're never pushing more than one conversation view
controller, which was previously a problem since we were "pushing" a newly
constructed VC in response to these myriad actions. It turned out there were
certain code paths that caused multiple actions to be fired in rapid succession
which pushed multiple ConversationVC's.
The built-in method: `setViewControllers:animated` easily ensures we only have
one ConversationVC on the stack, while being composable enough to faciliate the
various more efficient animations we desire.
The only thing lost with the complex methods is that the naive
`presentViewController:` can fail, e.g. if another view is already presented.
E.g. if an alert appears *just* before the user taps compose, the contact
picker will fail to present.
Since we no longer depend on this for presenting the CallViewController, this
isn't catostrophic, and in fact, arguable preferable, since we want the user to
read and dismiss any alert explicitly.
// FREEBIE
2018-08-18 22:54:35 +02:00
|
|
|
ConversationViewController *conversationVC = [ConversationViewController new];
|
|
|
|
[conversationVC configureForThread:thread action:action focusMessageId:focusMessageId];
|
2017-07-10 22:04:03 +02:00
|
|
|
self.lastThread = thread;
|
2015-12-26 17:27:27 +01:00
|
|
|
|
Faster conversation presentation.
There are multiple places in the codebase we present a conversation.
We used to have some very conservative machinery around how this was done, for
fear of failing to present the call view controller, which would have left a
hidden call in the background. We've since addressed that concern more
thoroughly via the separate calling UIWindow.
As such, the remaining presentation machinery is overly complex and inflexible
for what we need.
Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members)
Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation)
Sometimes we want to present the conversation with no animation (becoming active from a notification)
We also want to ensure that we're never pushing more than one conversation view
controller, which was previously a problem since we were "pushing" a newly
constructed VC in response to these myriad actions. It turned out there were
certain code paths that caused multiple actions to be fired in rapid succession
which pushed multiple ConversationVC's.
The built-in method: `setViewControllers:animated` easily ensures we only have
one ConversationVC on the stack, while being composable enough to faciliate the
various more efficient animations we desire.
The only thing lost with the complex methods is that the naive
`presentViewController:` can fail, e.g. if another view is already presented.
E.g. if an alert appears *just* before the user taps compose, the contact
picker will fail to present.
Since we no longer depend on this for presenting the CallViewController, this
isn't catostrophic, and in fact, arguable preferable, since we want the user to
read and dismiss any alert explicitly.
// FREEBIE
2018-08-18 22:54:35 +02:00
|
|
|
[self.navigationController setViewControllers:@[ self, conversationVC ] animated:isAnimated];
|
2015-12-26 17:27:27 +01:00
|
|
|
});
|
2014-10-29 21:58:58 +01:00
|
|
|
}
|
|
|
|
|
2017-07-10 22:04:03 +02:00
|
|
|
#pragma mark - Groupings
|
2014-10-29 21:58:58 +01:00
|
|
|
|
2017-07-18 21:18:52 +02:00
|
|
|
- (YapDatabaseViewMappings *)threadMappings
|
|
|
|
{
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(_threadMappings != nil);
|
2017-07-18 21:18:52 +02:00
|
|
|
return _threadMappings;
|
|
|
|
}
|
|
|
|
|
2017-07-14 20:06:19 +02:00
|
|
|
- (void)showInboxGrouping
|
2017-07-10 22:04:03 +02:00
|
|
|
{
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(self.homeViewMode == HomeViewMode_Archive);
|
2015-01-14 22:30:01 +01:00
|
|
|
|
2018-04-23 18:42:58 +02:00
|
|
|
[self.navigationController popToRootViewControllerAnimated:YES];
|
2017-07-14 20:06:19 +02:00
|
|
|
}
|
|
|
|
|
2018-04-23 18:42:58 +02:00
|
|
|
- (void)showArchivedConversations
|
2017-07-14 20:06:19 +02:00
|
|
|
{
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(self.homeViewMode == HomeViewMode_Inbox);
|
2018-04-23 18:42:58 +02:00
|
|
|
|
2018-04-26 17:50:33 +02:00
|
|
|
// When showing archived conversations, we want to use a conventional "back" button
|
|
|
|
// to return to the "inbox" home view.
|
|
|
|
[self applyArchiveBackButton];
|
|
|
|
|
2018-04-23 18:42:58 +02:00
|
|
|
// Push a separate instance of this view using "archive" mode.
|
|
|
|
HomeViewController *homeView = [HomeViewController new];
|
|
|
|
homeView.homeViewMode = HomeViewMode_Archive;
|
|
|
|
[self.navigationController pushViewController:homeView animated:YES];
|
2015-01-14 22:30:01 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 18:52:30 +02:00
|
|
|
- (NSString *)currentGrouping
|
|
|
|
{
|
2018-04-23 18:33:23 +02:00
|
|
|
switch (self.homeViewMode) {
|
|
|
|
case HomeViewMode_Inbox:
|
|
|
|
return TSInboxGroup;
|
|
|
|
case HomeViewMode_Archive:
|
|
|
|
return TSArchiveGroup;
|
|
|
|
}
|
2017-07-25 18:52:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateMappings
|
|
|
|
{
|
2017-12-19 17:38:25 +01:00
|
|
|
OWSAssertIsOnMainThread();
|
2017-05-31 23:49:21 +02:00
|
|
|
|
2018-07-31 02:31:08 +02:00
|
|
|
self.threadMappings = [[YapDatabaseViewMappings alloc]
|
|
|
|
initWithGroups:@[ kReminderViewPseudoGroup, self.currentGrouping, kArchiveButtonPseudoGroup ]
|
|
|
|
view:TSThreadDatabaseViewExtensionName];
|
2017-07-25 18:52:30 +02:00
|
|
|
[self.threadMappings setIsReversed:YES forGroup:self.currentGrouping];
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2017-07-26 18:39:43 +02:00
|
|
|
[self resetMappings];
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2018-04-21 21:22:40 +02:00
|
|
|
[self reloadTableViewData];
|
2017-05-31 23:49:21 +02:00
|
|
|
[self checkIfEmptyView];
|
2017-07-10 22:38:18 +02:00
|
|
|
[self updateReminderViews];
|
2014-10-29 21:58:58 +01:00
|
|
|
}
|
2014-11-21 14:38:37 +01:00
|
|
|
|
|
|
|
#pragma mark Database delegates
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (YapDatabaseConnection *)uiDatabaseConnection
|
|
|
|
{
|
2017-12-19 17:38:25 +01:00
|
|
|
OWSAssertIsOnMainThread();
|
2017-12-19 00:05:27 +01:00
|
|
|
|
2014-11-21 14:38:37 +01:00
|
|
|
if (!_uiDatabaseConnection) {
|
2018-03-05 15:30:58 +01:00
|
|
|
_uiDatabaseConnection = [OWSPrimaryStorage.sharedManager newDatabaseConnection];
|
2018-04-22 01:17:56 +02:00
|
|
|
// default is 250
|
|
|
|
_uiDatabaseConnection.objectCacheLimit = 500;
|
2014-11-21 14:38:37 +01:00
|
|
|
[_uiDatabaseConnection beginLongLivedReadTransaction];
|
|
|
|
}
|
|
|
|
return _uiDatabaseConnection;
|
|
|
|
}
|
|
|
|
|
2017-12-12 22:31:03 +01:00
|
|
|
- (void)yapDatabaseModifiedExternally:(NSNotification *)notification
|
|
|
|
{
|
2017-12-19 17:38:25 +01:00
|
|
|
OWSAssertIsOnMainThread();
|
2017-12-12 22:31:03 +01:00
|
|
|
|
2018-08-27 18:09:39 +02:00
|
|
|
OWSLogVerbose(@"");
|
2017-12-12 22:31:03 +01:00
|
|
|
|
2018-02-22 00:39:07 +01:00
|
|
|
if (self.shouldObserveDBModifications) {
|
|
|
|
// External database modifications can't be converted into incremental updates,
|
|
|
|
// so rebuild everything. This is expensive and usually isn't necessary, but
|
|
|
|
// there's no alternative.
|
2018-02-22 18:07:11 +01:00
|
|
|
//
|
|
|
|
// We don't need to do this if we're not observing db modifications since we'll
|
|
|
|
// do it when we resume.
|
2018-02-22 00:39:07 +01:00
|
|
|
[self resetMappings];
|
|
|
|
}
|
2017-12-12 22:31:03 +01:00
|
|
|
}
|
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
- (void)yapDatabaseModified:(NSNotification *)notification
|
|
|
|
{
|
2017-12-19 17:38:25 +01:00
|
|
|
OWSAssertIsOnMainThread();
|
2017-12-12 22:31:03 +01:00
|
|
|
|
2017-07-25 18:52:30 +02:00
|
|
|
if (!self.shouldObserveDBModifications) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-08-27 18:09:39 +02:00
|
|
|
OWSLogVerbose(@"");
|
2017-12-12 22:31:03 +01:00
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction];
|
2017-07-25 18:52:30 +02:00
|
|
|
|
|
|
|
if (![[self.uiDatabaseConnection ext:TSThreadDatabaseViewExtensionName] hasChangesForGroup:self.currentGrouping
|
|
|
|
inNotifications:notifications]) {
|
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
2018-04-23 20:13:55 +02:00
|
|
|
[self.threadMappings updateWithTransaction:transaction];
|
2017-07-25 18:52:30 +02:00
|
|
|
}];
|
2018-04-23 19:49:11 +02:00
|
|
|
[self checkIfEmptyView];
|
|
|
|
|
2017-07-25 18:52:30 +02:00
|
|
|
return;
|
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2017-05-01 20:28:37 +02:00
|
|
|
// If the user hasn't already granted contact access
|
|
|
|
// we don't want to request until they receive a message.
|
2018-04-21 17:12:58 +02:00
|
|
|
__block BOOL hasAnyMessages;
|
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
|
|
|
|
hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction];
|
|
|
|
}];
|
|
|
|
|
|
|
|
if (hasAnyMessages) {
|
2017-05-01 20:28:37 +02:00
|
|
|
[self.contactsManager requestSystemContactsOnce];
|
|
|
|
}
|
|
|
|
|
2017-07-25 18:52:30 +02:00
|
|
|
NSArray *sectionChanges = nil;
|
|
|
|
NSArray *rowChanges = nil;
|
2014-11-21 14:38:37 +01:00
|
|
|
[[self.uiDatabaseConnection ext:TSThreadDatabaseViewExtensionName] getSectionChanges:§ionChanges
|
|
|
|
rowChanges:&rowChanges
|
|
|
|
forNotifications:notifications
|
|
|
|
withMappings:self.threadMappings];
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2016-09-01 17:14:58 +02:00
|
|
|
// We want this regardless of if we're currently viewing the archive.
|
|
|
|
// So we run it before the early return
|
2017-07-14 20:14:41 +02:00
|
|
|
[self checkIfEmptyView];
|
2016-09-01 17:14:58 +02:00
|
|
|
|
2015-12-22 12:45:09 +01:00
|
|
|
if ([sectionChanges count] == 0 && [rowChanges count] == 0) {
|
2014-11-21 14:38:37 +01:00
|
|
|
return;
|
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2018-04-26 21:56:56 +02:00
|
|
|
if ([self updateHasArchivedThreadsRow]) {
|
|
|
|
[self.tableView reloadData];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-11-21 14:38:37 +01:00
|
|
|
[self.tableView beginUpdates];
|
2015-12-22 12:45:09 +01:00
|
|
|
|
|
|
|
for (YapDatabaseViewSectionChange *sectionChange in sectionChanges) {
|
|
|
|
switch (sectionChange.type) {
|
|
|
|
case YapDatabaseViewChangeDelete: {
|
2014-11-21 14:38:37 +01:00
|
|
|
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionChange.index]
|
|
|
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
|
|
|
break;
|
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
case YapDatabaseViewChangeInsert: {
|
2014-11-21 14:38:37 +01:00
|
|
|
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionChange.index]
|
|
|
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case YapDatabaseViewChangeUpdate:
|
|
|
|
case YapDatabaseViewChangeMove:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
|
|
|
|
for (YapDatabaseViewRowChange *rowChange in rowChanges) {
|
2018-04-21 19:00:58 +02:00
|
|
|
NSString *key = rowChange.collectionKey.key;
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(key);
|
2018-04-23 18:15:21 +02:00
|
|
|
[self.threadViewModelCache removeObjectForKey:key];
|
2018-04-21 19:00:58 +02:00
|
|
|
|
2015-12-22 12:45:09 +01:00
|
|
|
switch (rowChange.type) {
|
|
|
|
case YapDatabaseViewChangeDelete: {
|
2014-11-21 14:38:37 +01:00
|
|
|
[self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ]
|
|
|
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
|
|
|
break;
|
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
case YapDatabaseViewChangeInsert: {
|
2014-11-21 14:38:37 +01:00
|
|
|
[self.tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ]
|
|
|
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
|
|
|
break;
|
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
case YapDatabaseViewChangeMove: {
|
2014-11-21 14:38:37 +01:00
|
|
|
[self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ]
|
|
|
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
|
|
|
[self.tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ]
|
|
|
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
|
|
|
break;
|
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
case YapDatabaseViewChangeUpdate: {
|
2014-11-21 14:38:37 +01:00
|
|
|
[self.tableView reloadRowsAtIndexPaths:@[ rowChange.indexPath ]
|
|
|
|
withRowAnimation:UITableViewRowAnimationNone];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2014-11-21 14:38:37 +01:00
|
|
|
[self.tableView endUpdates];
|
|
|
|
}
|
2014-11-25 19:06:09 +01:00
|
|
|
|
2018-04-26 21:41:31 +02:00
|
|
|
- (NSUInteger)numberOfThreadsInGroup:(NSString *)group
|
2017-09-06 19:59:39 +02:00
|
|
|
{
|
2018-04-23 19:49:11 +02:00
|
|
|
// We need to consult the db view, not the mapping since the mapping only knows about
|
|
|
|
// the current group.
|
2018-04-26 21:41:31 +02:00
|
|
|
__block NSUInteger result;
|
2018-04-23 19:49:11 +02:00
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
|
|
YapDatabaseViewTransaction *viewTransaction = [transaction ext:TSThreadDatabaseViewExtensionName];
|
2018-04-26 21:41:31 +02:00
|
|
|
result = [viewTransaction numberOfItemsInGroup:group];
|
2018-04-23 19:49:11 +02:00
|
|
|
}];
|
2018-04-26 21:41:31 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSUInteger)numberOfInboxThreads
|
|
|
|
{
|
|
|
|
return [self numberOfThreadsInGroup:TSInboxGroup];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSUInteger)numberOfArchivedThreads
|
|
|
|
{
|
|
|
|
return [self numberOfThreadsInGroup:TSArchiveGroup];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)checkIfEmptyView
|
|
|
|
{
|
|
|
|
NSUInteger inboxCount = self.numberOfInboxThreads;
|
|
|
|
NSUInteger archiveCount = self.numberOfArchivedThreads;
|
2018-04-23 19:49:11 +02:00
|
|
|
|
|
|
|
if (self.homeViewMode == HomeViewMode_Inbox && inboxCount == 0 && archiveCount == 0) {
|
2018-04-24 17:42:04 +02:00
|
|
|
[self updateEmptyBoxText];
|
2015-01-27 02:20:11 +01:00
|
|
|
[_tableView setHidden:YES];
|
2018-04-23 19:49:11 +02:00
|
|
|
[_emptyBoxLabel setHidden:NO];
|
|
|
|
} else if (self.homeViewMode == HomeViewMode_Archive && archiveCount == 0) {
|
2018-04-24 17:42:04 +02:00
|
|
|
[self updateEmptyBoxText];
|
2015-01-27 02:20:11 +01:00
|
|
|
[_tableView setHidden:YES];
|
2018-04-23 19:49:11 +02:00
|
|
|
[_emptyBoxLabel setHidden:NO];
|
2016-11-14 19:31:24 +01:00
|
|
|
} else {
|
|
|
|
[_emptyBoxLabel setHidden:YES];
|
2018-04-23 19:49:11 +02:00
|
|
|
[_tableView setHidden:NO];
|
2014-11-25 19:06:09 +01:00
|
|
|
}
|
2015-01-27 21:17:49 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 17:42:04 +02:00
|
|
|
- (void)updateEmptyBoxText
|
2017-09-06 19:59:39 +02:00
|
|
|
{
|
2018-07-12 23:45:29 +02:00
|
|
|
// TODO: Theme, review with design.
|
2017-09-06 19:59:39 +02:00
|
|
|
_emptyBoxLabel.textColor = [UIColor grayColor];
|
|
|
|
_emptyBoxLabel.font = [UIFont ows_regularFontWithSize:18.f];
|
2015-01-27 21:17:49 +01:00
|
|
|
_emptyBoxLabel.textAlignment = NSTextAlignmentCenter;
|
2015-12-22 12:45:09 +01:00
|
|
|
|
2017-09-06 19:59:39 +02:00
|
|
|
NSString *firstLine = @"";
|
2015-12-22 12:45:09 +01:00
|
|
|
NSString *secondLine = @"";
|
|
|
|
|
2018-04-23 18:33:23 +02:00
|
|
|
if (self.homeViewMode == HomeViewMode_Inbox) {
|
2018-08-08 18:04:59 +02:00
|
|
|
if ([Environment.preferences hasSentAMessage]) {
|
2018-08-08 00:10:16 +02:00
|
|
|
firstLine = NSLocalizedString(
|
|
|
|
@"EMPTY_INBOX_TITLE", @"Header text an existing user sees when viewing an empty inbox");
|
|
|
|
secondLine = NSLocalizedString(
|
|
|
|
@"EMPTY_INBOX_TEXT", @"Body text an existing user sees when viewing an empty inbox");
|
2015-12-22 12:45:09 +01:00
|
|
|
} else {
|
2018-08-08 00:10:16 +02:00
|
|
|
firstLine = NSLocalizedString(
|
|
|
|
@"EMPTY_INBOX_NEW_USER_TITLE", @"Header text a new user sees when viewing an empty inbox");
|
|
|
|
secondLine = NSLocalizedString(
|
|
|
|
@"EMPTY_INBOX_NEW_USER_TEXT", @"Body text a new user sees when viewing an empty inbox");
|
2015-02-09 17:31:22 +01:00
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
} else {
|
2018-09-06 19:01:24 +02:00
|
|
|
OWSAssertDebug(self.homeViewMode == HomeViewMode_Archive);
|
2018-08-08 00:10:16 +02:00
|
|
|
firstLine = NSLocalizedString(
|
|
|
|
@"EMPTY_ARCHIVE_TITLE", @"Header text an existing user sees when viewing an empty archive");
|
|
|
|
secondLine = NSLocalizedString(
|
|
|
|
@"EMPTY_ARCHIVE_TEXT", @"Body text an existing user sees when viewing an empty archive");
|
2015-01-27 21:17:49 +01:00
|
|
|
}
|
2015-12-22 12:45:09 +01:00
|
|
|
NSMutableAttributedString *fullLabelString =
|
|
|
|
[[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n%@", firstLine, secondLine]];
|
|
|
|
|
|
|
|
[fullLabelString addAttribute:NSFontAttributeName
|
|
|
|
value:[UIFont ows_boldFontWithSize:15.f]
|
|
|
|
range:NSMakeRange(0, firstLine.length)];
|
|
|
|
[fullLabelString addAttribute:NSFontAttributeName
|
|
|
|
value:[UIFont ows_regularFontWithSize:14.f]
|
|
|
|
range:NSMakeRange(firstLine.length + 1, secondLine.length)];
|
|
|
|
[fullLabelString addAttribute:NSForegroundColorAttributeName
|
2018-07-13 15:50:49 +02:00
|
|
|
value:Theme.primaryColor
|
2015-12-22 12:45:09 +01:00
|
|
|
range:NSMakeRange(0, firstLine.length)];
|
2018-07-12 23:45:29 +02:00
|
|
|
// TODO: Theme, Review with design.
|
2015-12-22 12:45:09 +01:00
|
|
|
[fullLabelString addAttribute:NSForegroundColorAttributeName
|
2018-07-13 15:50:49 +02:00
|
|
|
value:Theme.secondaryColor
|
2015-12-22 12:45:09 +01:00
|
|
|
range:NSMakeRange(firstLine.length + 1, secondLine.length)];
|
2015-01-27 21:17:49 +01:00
|
|
|
_emptyBoxLabel.attributedText = fullLabelString;
|
2014-11-25 19:06:09 +01:00
|
|
|
}
|
|
|
|
|
2018-08-07 23:36:34 +02:00
|
|
|
// We want to delay asking for a review until an opportune time.
|
|
|
|
// If the user has *just* launched Signal they intend to do something, we don't want to interrupt them.
|
|
|
|
// If the user hasn't sent a message, we don't want to ask them for a review yet.
|
|
|
|
- (void)requestReviewIfAppropriate
|
|
|
|
{
|
2018-08-08 18:04:59 +02:00
|
|
|
if (self.hasEverAppeared && Environment.preferences.hasSentAMessage) {
|
2018-08-27 18:12:53 +02:00
|
|
|
OWSLogDebug(@"requesting review");
|
2018-08-07 23:36:34 +02:00
|
|
|
if (@available(iOS 10, *)) {
|
|
|
|
// In Debug this pops up *every* time, which is helpful, but annoying.
|
|
|
|
// In Production this will pop up at most 3 times per 365 days.
|
|
|
|
#ifndef DEBUG
|
|
|
|
[SKStoreReviewController requestReview];
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
} else {
|
2018-08-27 18:12:53 +02:00
|
|
|
OWSLogDebug(@"not requesting review");
|
2018-08-07 23:36:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
@end
|
2018-06-12 17:27:32 +02:00
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_END
|