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 >
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 ,
} ;
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-06-21 15:59:01 +02:00
UISearchBarDelegate ,
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 ;
2017-08-16 16:25:36 +02:00
@ property ( nonatomic ) BOOL hasBeenPresented ;
2017-05-31 23:49:21 +02:00
2018-06-08 03:51:42 +02:00
// Mark : Search
2018-06-11 18:15:46 +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-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 ;
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
{
2017-07-10 22:04:03 +02:00
OWSFail ( @ "Do not load this from the storyboard." ) ;
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 ;
_contactsManager = [ Environment current ] . contactsManager ;
_messageSender = [ Environment current ] . 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 : )
name : NSNotificationNameThemeDidChange
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-07-12 23:45:29 +02:00
- ( void ) themeDidChange : ( id ) notification
{
OWSAssertIsOnMainThread ( ) ;
[ self applyTheme ] ;
[ self . tableView reloadData ] ;
}
- ( void ) applyTheme
{
OWSAssertIsOnMainThread ( ) ;
self . view . backgroundColor = UIColor . ows_themeBackgroundColor ;
self . tableView . backgroundColor = UIColor . ows_themeBackgroundColor ;
}
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 ] ;
reminderStackView . axis = UILayoutConstraintAxisVertical ;
reminderStackView . spacing = 0 ;
[ self . view addSubview : reminderStackView ] ;
[ reminderStackView autoPinWidthToSuperview ] ;
[ reminderStackView autoPinToTopLayoutGuideOfViewController : self withInset : 0 ] ;
2018-07-04 01:52:43 +02:00
// 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 ] ;
} ] ;
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-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 ] ;
[ self . tableView autoPinWidthToSuperview ] ;
2018-04-02 21:59:32 +02:00
[ self . tableView autoPinEdgeToSuperviewEdge : ALEdgeBottom ] ;
2018-06-30 00:49:24 +02:00
// TODO - have content scroll behind navbar will require changing this .
2018-06-20 21:15:33 +02:00
[ self . tableView autoPinEdge : ALEdgeTop toEdge : ALEdgeBottom ofView : reminderStackView ] ;
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 ] ;
2018-07-12 23:45:29 +02:00
[ self applyTheme ] ;
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 ;
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
UISearchBar * searchBar = [ UISearchBar new ] ;
_searchBar = searchBar ;
searchBar . searchBarStyle = UISearchBarStyleMinimal ;
searchBar . placeholder = NSLocalizedString ( @ "HOME_VIEW_CONVERSATION_SEARCHBAR_PLACEHOLDER" ,
@ "Placeholder text for search bar which filters conversations." ) ;
2018-07-12 23:45:29 +02:00
searchBar . backgroundColor = UIColor . ows_themeBackgroundColor ;
if ( UIColor . isThemeEnabled ) {
searchBar . barStyle = UIBarStyleBlack ;
} else {
searchBar . barStyle = UIBarStyleDefault ;
}
2018-06-11 18:15:46 +02:00
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-06-11 18:15:46 +02:00
OWSAssert ( self . tableView . tableHeaderView = = nil ) ;
self . tableView . tableHeaderView = self . searchBar ;
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 ] ;
[ searchResultsController . view autoPinWidthToSuperview ] ;
[ searchResultsController . view autoPinEdge : ALEdgeTop toEdge : ALEdgeBottom ofView : searchBar ] ;
[ searchResultsController . view autoPinEdge : ALEdgeBottom toEdge : ALEdgeBottom ofView : self . tableView ] ;
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 ] ;
}
- ( 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-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 .
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-05-14 17:47:13 +02:00
if ( [ self isIndexPathForArchivedConversations : indexPath ] ) {
return nil ;
}
2015-12-26 17:27:27 +01:00
if ( indexPath ) {
[ previewingContext setSourceRect : [ self . tableView rectForRowAtIndexPath : indexPath ] ] ;
2017-09-06 20:13:18 +02:00
ConversationViewController * vc = [ ConversationViewController new ] ;
2017-09-06 19:59:39 +02:00
TSThread * thread = [ self threadForIndexPath : indexPath ] ;
2017-07-10 22:04:03 +02:00
self . lastThread = thread ;
2018-06-11 21:31:54 +02:00
[ vc configureForThread : thread action : ConversationViewActionNone focusMessageId : nil ] ;
2015-12-26 17:27:27 +01:00
[ vc peekSetup ] ;
2015-12-22 12:45:09 +01:00
2015-12-26 17:27:27 +01:00
return vc ;
} else {
return nil ;
}
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 ( ) ;
DDLogInfo ( @ "%@ %s" , self . logTag , __PRETTY _FUNCTION __ ) ;
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 ) {
2017-11-08 20:04:51 +01:00
DDLogError ( @ "%@ Error when requesting contacts: %@" , self . logTag , 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 .
2017-08-17 18:37:21 +02:00
OWSNavigationController * navigationController =
[ [ OWSNavigationController alloc ] initWithRootViewController : viewController ] ;
2017-05-08 22:28:01 +02:00
[ self presentTopLevelModalViewController : navigationController animateDismissal : YES animatePresentation : YES ] ;
} ] ;
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 ) {
OWSAssert ( self . searchBar . text . ows_stripped . length > 0 ) ;
self . tableView . contentOffset = CGPointZero ;
} else if ( self . lastThread ) {
OWSAssert ( self . searchBar . text . ows_stripped . length = = 0 ) ;
// 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 ] ;
}
2017-05-31 23:49:21 +02:00
}
- ( void ) viewWillDisappear : ( BOOL ) animated
{
[ super viewWillDisappear : animated ] ;
self . isViewVisible = NO ;
}
- ( 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 ] ;
2017-08-16 16:25:36 +02:00
} else if ( ! self . hasBeenPresented && [ ProfileViewController shouldDisplayProfileViewOnLaunch ] ) {
[ ProfileViewController presentForUpgradeOrNag : self ] ;
2018-02-13 21:00:25 +01:00
} else {
[ OWSAlerts showIOSUpgradeNagIfNecessary ] ;
2017-02-27 17:04:14 +01:00
}
2017-08-16 16:25:36 +02:00
self . hasBeenPresented = YES ;
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
}
2017-09-06 19:59:39 +02:00
- ( NSInteger ) tableView : ( UITableView * ) tableView numberOfRowsInSection : ( NSInteger ) section
{
2018-04-23 18:33:23 +02:00
NSInteger result = ( NSInteger ) [ self . threadMappings numberOfItemsInSection : ( NSUInteger ) section ] ;
2018-04-26 21:56:56 +02:00
if ( self . hasArchivedThreadsRow ) {
2018-04-23 18:33:23 +02:00
// Add the "archived conversations" row .
result + + ;
}
return result ;
}
- ( BOOL ) isIndexPathForArchivedConversations : ( NSIndexPath * ) indexPath
{
if ( self . homeViewMode ! = HomeViewMode_Inbox ) {
return NO ;
}
if ( indexPath . section ! = 0 ) {
return NO ;
}
NSInteger cellCount = ( NSInteger ) [ self . threadMappings numberOfItemsInSection : ( NSUInteger ) 0 ] ;
return indexPath . row = = cellCount ;
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-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
{
if ( [ self isIndexPathForArchivedConversations : indexPath ] ) {
return [ self cellForArchivedConversationsRow : tableView ] ;
} else {
return [ self tableView : tableView cellForConversationAtIndexPath : indexPath ] ;
}
}
- ( UITableViewCell * ) tableView : ( UITableView * ) tableView cellForConversationAtIndexPath : ( NSIndexPath * ) indexPath
2018-04-21 19:00:58 +02:00
{
HomeViewCell * cell = [ self . tableView dequeueReusableCellWithIdentifier : HomeViewCell . cellReuseIdentifier ] ;
OWSAssert ( cell ) ;
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 ] ;
2015-12-22 12:45:09 +01:00
if ( ( unsigned long ) indexPath . row = = [ self . threadMappings numberOfItemsInSection : 0 ] - 1 ) {
2015-03-20 14:32:57 +01:00
cell . separatorInset = UIEdgeInsetsMake ( 0. f , cell . bounds . size . width , 0. f , 0. f ) ;
}
2015-12-22 12:45:09 +01:00
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 ] ;
OWSAssert ( cell ) ;
for ( UIView * subview in cell . contentView . subviews ) {
[ subview removeFromSuperview ] ;
}
2018-07-12 23:45:29 +02:00
cell . backgroundColor = UIColor . ows_themeBackgroundColor ;
2018-04-23 18:33:23 +02:00
2018-06-29 23:00:22 +02:00
UIImage * disclosureImage = [ UIImage imageNamed : ( CurrentAppContext ( ) . isRTL ? @ "NavBarBack" : @ "NavBarBackRTL" ) ] ;
2018-04-23 18:33:23 +02:00
OWSAssert ( disclosureImage ) ;
UIImageView * disclosureImageView = [ UIImageView new ] ;
disclosureImageView . image = [ disclosureImage imageWithRenderingMode : UIImageRenderingModeAlwaysTemplate ] ;
disclosureImageView . tintColor = [ UIColor colorWithRGBHex : 0 xd1d1d6 ] ;
[ 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 00:38:55 +02:00
label . textColor = UIColor . ows_themePrimaryColor ;
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 ( ) ;
2017-11-08 20:04:51 +01:00
DDLogInfo ( @ "%@ beggining refreshing." , self . logTag ) ;
2017-12-04 16:35:47 +01:00
[ SignalApp . sharedApp . messageFetcherJob run ] . always ( ^ {
2017-11-08 20:04:51 +01:00
DDLogInfo ( @ "%@ ending refreshing." , self . logTag ) ;
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-04-23 18:33:23 +02:00
if ( [ self isIndexPathForArchivedConversations : indexPath ] ) {
return @ [ ] ;
}
2015-12-22 12:45:09 +01:00
UITableViewRowAction * deleteAction =
[ UITableViewRowAction rowActionWithStyle : UITableViewRowActionStyleDefault
2015-12-26 17:27:27 +01:00
title : NSLocalizedString ( @ "TXT_DELETE_TITLE" , nil )
2015-12-22 12:45:09 +01:00
handler : ^ ( UITableViewRowAction * action , NSIndexPath * swipedIndexPath ) {
2017-09-06 19:59:39 +02:00
[ self tableViewCellTappedDelete : swipedIndexPath ] ;
2015-12-22 12:45:09 +01:00
} ] ;
2015-12-26 17:27:27 +01:00
UITableViewRowAction * archiveAction ;
2018-04-23 18:33:23 +02:00
if ( self . homeViewMode = = HomeViewMode_Inbox ) {
2015-12-26 17:27:27 +01:00
archiveAction = [ UITableViewRowAction
rowActionWithStyle : UITableViewRowActionStyleNormal
2017-09-06 19:59:39 +02:00
title : NSLocalizedString ( @ "ARCHIVE_ACTION" ,
@ "Pressing this button moves a thread from the inbox to the archive" )
2015-12-26 17:27:27 +01:00
handler : ^ ( UITableViewRowAction * _Nonnull action , NSIndexPath * _Nonnull tappedIndexPath ) {
2017-09-06 19:59:39 +02:00
[ self archiveIndexPath : tappedIndexPath ] ;
[ Environment . preferences setHasArchivedAMessage : YES ] ;
2015-12-26 17:27:27 +01:00
} ] ;
2015-12-22 12:45:09 +01:00
2015-12-26 17:27:27 +01:00
} else {
archiveAction = [ UITableViewRowAction
rowActionWithStyle : UITableViewRowActionStyleNormal
2017-09-06 19:59:39 +02:00
title : NSLocalizedString ( @ "UNARCHIVE_ACTION" ,
@ "Pressing this button moves an archived thread from the archive back to the inbox" )
2015-12-26 17:27:27 +01:00
handler : ^ ( UITableViewRowAction * _Nonnull action , NSIndexPath * _Nonnull tappedIndexPath ) {
2017-09-06 19:59:39 +02:00
[ self archiveIndexPath : tappedIndexPath ] ;
2015-12-26 17:27:27 +01:00
} ] ;
}
2015-12-22 12:45:09 +01:00
2015-12-26 17:27:27 +01:00
return @ [ deleteAction , archiveAction ] ;
2015-01-14 22:30:01 +01:00
}
2017-09-06 19:59:39 +02:00
- ( BOOL ) tableView : ( UITableView * ) tableView canEditRowAtIndexPath : ( NSIndexPath * ) indexPath
{
2018-05-14 17:47:13 +02:00
if ( [ self isIndexPathForArchivedConversations : indexPath ] ) {
return NO ;
}
2015-01-14 22:30:01 +01:00
return YES ;
}
2018-06-11 18:15:46 +02:00
# pragma mark - UISearchBarDelegate
- ( void ) searchBarTextDidBeginEditing : ( UISearchBar * ) searchBar
{
2018-06-11 18:18:43 +02:00
[ self . tableView setContentOffset : CGPointZero animated : NO ] ;
2018-06-11 18:15:46 +02:00
[ self updateSearchResultsVisibility ] ;
2018-06-21 15:59:01 +02:00
[ self ensureSearchBarCancelButton ] ;
2018-06-11 18:15:46 +02:00
}
- ( void ) searchBarTextDidEndEditing : ( UISearchBar * ) searchBar
{
[ self updateSearchResultsVisibility ] ;
2018-06-21 15:59:01 +02:00
[ self ensureSearchBarCancelButton ] ;
2018-06-11 18:15:46 +02:00
}
2018-06-09 12:52:03 +02:00
2018-06-11 18:15:46 +02:00
- ( void ) searchBar : ( UISearchBar * ) searchBar textDidChange : ( NSString * ) searchText
2018-06-09 12:52:03 +02:00
{
2018-06-11 18:15:46 +02:00
[ self updateSearchResultsVisibility ] ;
2018-06-21 15:59:01 +02:00
[ self ensureSearchBarCancelButton ] ;
2018-06-11 18:15:46 +02:00
}
- ( void ) searchBarSearchButtonClicked : ( UISearchBar * ) searchBar
{
[ self updateSearchResultsVisibility ] ;
}
- ( void ) searchBarCancelButtonClicked : ( UISearchBar * ) searchBar
{
self . searchBar . text = nil ;
2018-06-21 15:59:01 +02:00
[ self . searchBar resignFirstResponder ] ;
OWSAssert ( ! self . searchBar . isFirstResponder ) ;
2018-06-11 18:15:46 +02:00
[ self updateSearchResultsVisibility ] ;
2018-06-21 15:59:01 +02:00
[ 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 ) {
[ self . tableView setContentOffset : CGPointZero animated : NO ] ;
self . tableView . scrollEnabled = NO ;
} else {
self . tableView . scrollEnabled = YES ;
}
2018-06-09 12:52:03 +02:00
}
2018-06-21 15:59:01 +02:00
# pragma mark - UIScrollViewDelegate
- ( void ) scrollViewWillBeginDragging : ( UIScrollView * ) scrollView
{
[ self . searchBar resignFirstResponder ] ;
OWSAssert ( ! self . searchBar . isFirstResponder ) ;
}
# pragma mark - ConversationSearchViewDelegate
- ( void ) conversationSearchViewWillBeginDragging
{
[ self . searchBar resignFirstResponder ] ;
OWSAssert ( ! self . searchBar . isFirstResponder ) ;
}
2014-10-29 21:58:58 +01:00
# pragma mark - HomeFeedTableViewCellDelegate
2017-09-06 19:59:39 +02:00
- ( void ) tableViewCellTappedDelete : ( NSIndexPath * ) indexPath
{
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
groupMetaMessage : TSGroupMessageQuit
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
{
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-06-22 20:40:48 +02:00
DDLogInfo ( @ "%@ %s %ld %ld" , self . logTag , __PRETTY _FUNCTION __ , ( long ) indexPath . row , ( long ) indexPath . section ) ;
[ self . searchBar resignFirstResponder ] ;
2018-05-10 23:25:26 +02:00
2018-04-23 18:33:23 +02:00
if ( [ self isIndexPathForArchivedConversations : indexPath ] ) {
2018-04-23 18:42:58 +02:00
[ self showArchivedConversations ] ;
2018-04-23 18:33:23 +02:00
return ;
2015-02-10 12:02:58 +01:00
}
2015-12-22 12:45:09 +01:00
2015-12-26 17:27:27 +01:00
TSThread * thread = [ self threadForIndexPath : indexPath ] ;
2018-05-02 16:08:47 +02:00
[ self presentThread : thread action : ConversationViewActionNone ] ;
2015-12-26 17:27:27 +01:00
[ tableView deselectRowAtIndexPath : indexPath animated : YES ] ;
}
2018-05-02 16:08:47 +02:00
- ( void ) presentThread : ( TSThread * ) thread action : ( ConversationViewAction ) action
2018-06-11 21:31:54 +02:00
{
[ self presentThread : thread action : action focusMessageId : nil ] ;
}
- ( void ) presentThread : ( TSThread * ) thread
action : ( ConversationViewAction ) action
focusMessageId : ( nullable NSString * ) focusMessageId
2017-04-18 22:08:01 +02:00
{
2017-08-31 18:31:13 +02:00
if ( thread = = nil ) {
OWSFail ( @ "Thread unexpectedly nil" ) ;
return ;
}
2017-08-03 19:16:45 +02:00
// We do this synchronously if we ' re already on the main thread .
DispatchMainThreadSafe ( ^ {
2018-06-11 21:31:54 +02:00
ConversationViewController * viewController = [ ConversationViewController new ] ;
[ viewController 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
2018-06-11 21:31:54 +02:00
[ self pushTopLevelViewController : viewController animateDismissal : YES animatePresentation : YES ] ;
2015-12-26 17:27:27 +01:00
} ) ;
2014-10-29 21:58:58 +01:00
}
2017-04-25 18:56:37 +02:00
- ( void ) presentTopLevelModalViewController : ( UIViewController * ) viewController
animateDismissal : ( BOOL ) animateDismissal
animatePresentation : ( BOOL ) animatePresentation
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread ( ) ;
2017-04-25 18:56:37 +02:00
OWSAssert ( viewController ) ;
[ self presentViewControllerWithBlock : ^ {
[ self presentViewController : viewController animated : animatePresentation completion : nil ] ;
}
animateDismissal : animateDismissal ] ;
}
- ( void ) pushTopLevelViewController : ( UIViewController * ) viewController
animateDismissal : ( BOOL ) animateDismissal
animatePresentation : ( BOOL ) animatePresentation
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread ( ) ;
2017-04-25 18:56:37 +02:00
OWSAssert ( viewController ) ;
[ self presentViewControllerWithBlock : ^ {
[ self . navigationController pushViewController : viewController animated : animatePresentation ] ;
}
animateDismissal : animateDismissal ] ;
}
2017-11-08 20:04:51 +01:00
- ( void ) presentViewControllerWithBlock : ( void ( ^ ) ( void ) ) presentationBlock animateDismissal : ( BOOL ) animateDismissal
2017-04-25 18:56:37 +02:00
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread ( ) ;
2017-04-25 18:56:37 +02:00
OWSAssert ( presentationBlock ) ;
// Presenting a "top level" view controller has three steps :
//
// First , dismiss any presented modal .
// Second , pop to the root view controller if necessary .
// Third present the new view controller using presentationBlock .
// Define a block to perform the second step .
2017-11-08 20:04:51 +01:00
void ( ^ dismissNavigationBlock ) ( void ) = ^ {
2017-04-25 18:56:37 +02:00
if ( self . navigationController . viewControllers . lastObject ! = self ) {
[ CATransaction begin ] ;
[ CATransaction setCompletionBlock : ^ {
presentationBlock ( ) ;
} ] ;
[ self . navigationController popToViewController : self animated : animateDismissal ] ;
[ CATransaction commit ] ;
} else {
presentationBlock ( ) ;
}
} ;
// Perform the first step .
if ( self . presentedViewController ) {
2017-08-03 16:40:45 +02:00
if ( [ self . presentedViewController isKindOfClass : [ CallViewController class ] ] ) {
2017-08-03 16:41:16 +02:00
OWSProdInfo ( [ OWSAnalyticsEvents errorCouldNotPresentViewDueToCall ] ) ;
2017-08-03 16:40:45 +02:00
return ;
}
2017-04-25 18:56:37 +02:00
[ self . presentedViewController dismissViewControllerAnimated : animateDismissal completion : dismissNavigationBlock ] ;
} else {
dismissNavigationBlock ( ) ;
}
}
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
{
OWSAssert ( _threadMappings ! = nil ) ;
return _threadMappings ;
}
2017-07-14 20:06:19 +02:00
- ( void ) showInboxGrouping
2017-07-10 22:04:03 +02:00
{
2018-04-23 18:42:58 +02:00
OWSAssert ( 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-04-23 18:42:58 +02:00
OWSAssert ( self . homeViewMode = = HomeViewMode_Inbox ) ;
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
2017-07-25 18:52:30 +02:00
self . threadMappings = [ [ YapDatabaseViewMappings alloc ] initWithGroups : @ [ self . currentGrouping ]
view : TSThreadDatabaseViewExtensionName ] ;
[ 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
DDLogVerbose ( @ "%@ %s" , self . logTag , __PRETTY _FUNCTION __ ) ;
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 ;
}
2017-12-12 22:31:03 +01:00
DDLogVerbose ( @ "%@ %s" , self . logTag , __PRETTY _FUNCTION __ ) ;
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 : & sectionChanges
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 ;
OWSAssert ( 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 ) {
2015-12-22 12:45:09 +01:00
if ( [ Environment . preferences getHasSentAMessage ] ) {
2018-05-20 06:35:06 +02:00
// FIXME : This doesn ' t appear to ever show up as the defaults flag is never set ( setHasSentAMessage : is never called ) .
2017-09-06 19:59:39 +02:00
firstLine = NSLocalizedString ( @ "EMPTY_INBOX_FIRST_TITLE" , @ "" ) ;
2015-02-18 23:21:03 +01:00
secondLine = NSLocalizedString ( @ "EMPTY_INBOX_FIRST_TEXT" , @ "" ) ;
2015-12-22 12:45:09 +01:00
} else {
2018-05-20 06:35:06 +02:00
// FIXME : Misleading localizable string key name .
firstLine = NSLocalizedString ( @ "EMPTY_ARCHIVE_FIRST_TITLE" , @ "First (bolded) part of the label that shows up when there are neither active nor archived conversations" ) ;
secondLine = NSLocalizedString ( @ "EMPTY_ARCHIVE_FIRST_TEXT" , @ "Second part of the label that shows up when there are neither active nor archived conversations" ) ;
2015-02-09 17:31:22 +01:00
}
2015-12-22 12:45:09 +01:00
} else {
if ( [ Environment . preferences getHasArchivedAMessage ] ) {
2018-05-20 06:35:06 +02:00
// FIXME : Shows up after the archival tab is cleared up completely by the user , the localizable string key is misleading .
2017-09-06 19:59:39 +02:00
firstLine = NSLocalizedString ( @ "EMPTY_INBOX_TITLE" , @ "" ) ;
2015-12-22 12:45:09 +01:00
secondLine = NSLocalizedString ( @ "EMPTY_INBOX_TEXT" , @ "" ) ;
} else {
2017-09-06 19:59:39 +02:00
firstLine = NSLocalizedString ( @ "EMPTY_ARCHIVE_TITLE" , @ "" ) ;
2015-12-22 12:45:09 +01:00
secondLine = NSLocalizedString ( @ "EMPTY_ARCHIVE_TEXT" , @ "" ) ;
2015-02-09 17:31:22 +01:00
}
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 00:38:55 +02:00
value : UIColor . ows_themePrimaryColor
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-12 23:45:29 +02:00
value : UIColor . ows_themeSecondaryColor
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
}
2014-10-29 21:58:58 +01:00
@ end
2018-06-12 17:27:32 +02:00
NS_ASSUME _NONNULL _END