2014-10-29 21:58:58 +01:00
//
2017-02-02 23:22:36 +01:00
// Copyright ( c ) 2017 Open Whisper Systems . All rights reserved .
2014-10-29 21:58:58 +01:00
//
2016-10-10 22:02:09 +02:00
# import "SignalsViewController.h"
# import "AppDelegate.h"
# import "InboxTableViewCell.h"
2017-05-02 00:06:53 +02:00
# import "MessageComposeTableViewController.h"
2014-10-29 21:58:58 +01:00
# import "MessagesViewController.h"
2015-12-22 12:45:09 +01:00
# import "NSDate+millisecondTimeStamp.h"
2016-08-01 00:25:07 +02:00
# import "OWSContactsManager.h"
2016-10-10 22:02:09 +02:00
# import "PropertyListPreferences.h"
# import "PushManager.h"
# import "Signal-Swift.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"
2015-12-22 12:45:09 +01:00
# import "TSStorageManager.h"
2016-10-10 22:02:09 +02:00
# import "UIUtil.h"
2015-02-21 03:47:52 +01:00
# import "VersionMigrations.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 >
# import < SignalServiceKit / TSMessagesManager . h >
# import < SignalServiceKit / TSOutgoingMessage . 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
2015-01-06 07:06:19 +01:00
# define CELL_HEIGHT 72.0 f
2014-10-29 21:58:58 +01:00
# define HEADER_HEIGHT 44.0 f
2016-11-12 18:22:29 +01:00
NSString * const SignalsViewControllerSegueShowIncomingCall = @ "ShowIncomingCallSegue" ;
2014-12-05 23:38:13 +01:00
@ interface SignalsViewController ( )
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 ;
2015-01-14 22:30:01 +01:00
@ property ( nonatomic ) CellState viewingThreadsIn ;
@ property ( nonatomic ) long inboxCount ;
2017-04-08 16:47:47 +02:00
@ property ( nonatomic ) UISegmentedControl * segmentedControl ;
@ property ( nonatomic ) id previewingContext ;
2017-05-05 18:39:21 +02:00
@ property ( nonatomic ) NSSet < NSString * > * blockedPhoneNumberSet ;
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-02-27 17:04:14 +01:00
@ property ( nonatomic , readonly ) ExperienceUpgradeFinder * experienceUpgradeFinder ;
2016-10-14 22:59:58 +02:00
@ property ( nonatomic , readonly ) TSMessagesManager * messagesManager ;
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
2017-05-05 22:55:31 +02:00
@ property ( weak , nonatomic ) IBOutlet ReminderView * missingContactsPermissionView ;
2017-05-05 18:39:21 +02:00
@ property ( weak , nonatomic ) IBOutlet NSLayoutConstraint * hideMissingContactsPermissionViewConstraint ;
2014-10-29 21:58:58 +01:00
@ end
@ implementation SignalsViewController
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 ;
}
2016-11-12 18:22:29 +01:00
[ self commonInit ] ;
2016-09-21 14:37:51 +02:00
return self ;
}
- ( instancetype ) initWithCoder : ( NSCoder * ) aDecoder
{
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
{
_accountManager = [ Environment getCurrent ] . accountManager ;
_contactsManager = [ Environment getCurrent ] . contactsManager ;
_messagesManager = [ TSMessagesManager sharedManager ] ;
_messageSender = [ Environment getCurrent ] . messageSender ;
2017-04-08 16:47:47 +02:00
_blockingManager = [ OWSBlockingManager sharedManager ] ;
_blockedPhoneNumberSet = [ NSSet setWithArray : [ _blockingManager blockedPhoneNumbers ] ] ;
2017-02-27 17:04:14 +01:00
_experienceUpgradeFinder = [ ExperienceUpgradeFinder new ] ;
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-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
{
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
_blockedPhoneNumberSet = [ NSSet setWithArray : [ _blockingManager blockedPhoneNumbers ] ] ;
2017-05-05 18:39:21 +02:00
2017-04-08 16:47:47 +02:00
[ self . tableView reloadData ] ;
} ) ;
2016-11-12 18:22:29 +01:00
}
2017-05-01 20:10:53 +02:00
- ( void ) signalAccountsDidChange : ( id ) notification
{
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ self . tableView reloadData ] ;
} ) ;
}
2017-05-05 18:39:21 +02:00
# pragma mark - View Life Cycle
2016-09-21 14:37:51 +02:00
- ( void ) awakeFromNib
{
[ super awakeFromNib ] ;
2014-11-26 20:29:45 +01:00
[ [ Environment getCurrent ] setSignalsViewController : self ] ;
}
2014-10-29 21:58:58 +01:00
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
2015-01-14 22:30:01 +01:00
[ self . navigationController . navigationBar setTranslucent : NO ] ;
2015-12-22 12:45:09 +01:00
2014-12-05 23:38:13 +01:00
[ self tableViewSetUp ] ;
2015-12-22 12:45:09 +01:00
2014-12-05 23:38:13 +01:00
self . editingDbConnection = TSStorageManager . sharedManager . newDatabaseConnection ;
2015-12-22 12:45:09 +01:00
2014-11-21 14:38:37 +01:00
[ self . uiDatabaseConnection beginLongLivedReadTransaction ] ;
2015-12-22 12:45:09 +01:00
2014-11-21 14:38:37 +01:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( yapDatabaseModified : )
name : TSUIDatabaseConnectionDidUpdateNotification
object : nil ] ;
2015-01-14 22:30:01 +01:00
[ self selectedInbox : self ] ;
2015-12-22 12:45:09 +01:00
self . segmentedControl = [ [ UISegmentedControl alloc ] initWithItems : @ [
NSLocalizedString ( @ "WHISPER_NAV_BAR_TITLE" , nil ) ,
NSLocalizedString ( @ "ARCHIVE_NAV_BAR_TITLE" , nil )
] ] ;
[ self . segmentedControl addTarget : self
action : @ selector ( swappedSegmentedControl )
forControlEvents : UIControlEventValueChanged ] ;
2017-01-31 04:59:39 +01:00
UINavigationItem * navigationItem = self . navigationItem ;
navigationItem . titleView = self . segmentedControl ;
2015-05-23 15:54:50 +02:00
[ self . segmentedControl setSelectedSegmentIndex : 0 ] ;
2017-01-31 04:59:39 +01:00
navigationItem . leftBarButtonItem . accessibilityLabel = NSLocalizedString (
@ "SETTINGS_BUTTON_ACCESSIBILITY" , @ "Accessibility hint for the settings button" ) ;
2015-12-26 17:27:27 +01:00
2017-05-05 18:39:21 +02:00
self . missingContactsPermissionView . text = NSLocalizedString ( @ "INBOX_VIEW_MISSING_CONTACTS_PERMISSION" , @ "Multi line label explainging how to show names instead of phone numbers in your inbox" ) ;
self . missingContactsPermissionView . tapAction = ^ {
[ [ UIApplication sharedApplication ] openSystemSettings ] ;
} ;
// Should only have to do this once per load ( e . g . vs did appear ) since app restarts when permissions change .
self . hideMissingContactsPermissionViewConstraint . active = ! self . shouldShowMissingContactsPermissionView ;
2015-12-22 12:45:09 +01:00
if ( [ self . traitCollection respondsToSelector : @ selector ( forceTouchCapability ) ] &&
( self . traitCollection . forceTouchCapability = = UIForceTouchCapabilityAvailable ) ) {
2015-12-26 17:27:27 +01:00
[ self registerForPreviewingWithDelegate : self sourceView : self . tableView ] ;
2015-10-31 16:53:32 +01:00
}
2017-02-07 21:47:33 +01:00
2016-11-12 18:22:29 +01:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
2017-02-02 23:22:36 +01:00
selector : @ selector ( handleActiveCallNotification : )
2016-11-12 18:22:29 +01:00
name : [ CallService callServiceActiveCallNotificationName ]
object : nil ] ;
2017-02-17 18:00:10 +01:00
[ self updateBarButtonItems ] ;
}
- ( void ) updateBarButtonItems {
const CGFloat kBarButtonSize = 44 ;
if ( YES ) {
// We use UIButtons with [ UIBarButtonItem initWithCustomView : . . . ] instead of
// UIBarButtonItem in order to ensure that these buttons are spaced tightly .
// The contents of the navigation bar are cramped in this view .
UIButton * button = [ UIButton buttonWithType : UIButtonTypeCustom ] ;
UIImage * image = [ UIImage imageNamed : @ "button_settings_white" ] ;
[ button setImage : image
forState : UIControlStateNormal ] ;
UIEdgeInsets imageEdgeInsets = UIEdgeInsetsZero ;
// We normally would want to use left and right insets that ensure the button
// is square and the icon is centered . However UINavigationBar doesn ' t offer us
// control over the margins and spacing of its content , and the buttons end up
// too far apart and too far from the edge of the screen . So we use a smaller
// left inset tighten up the layout .
imageEdgeInsets . right = round ( ( kBarButtonSize - image . size . width ) * 0.5 f ) ;
2017-02-17 23:30:49 +01:00
imageEdgeInsets . left = round ( ( kBarButtonSize - ( image . size . width + imageEdgeInsets . right ) ) * 0.5 f ) ;
2017-02-17 18:00:10 +01:00
imageEdgeInsets . top = round ( ( kBarButtonSize - image . size . height ) * 0.5 f ) ;
imageEdgeInsets . bottom = round ( kBarButtonSize - ( image . size . height + imageEdgeInsets . top ) ) ;
button . imageEdgeInsets = imageEdgeInsets ;
button . accessibilityLabel = NSLocalizedString ( @ "OPEN_SETTINGS_BUTTON" , "Label for button which opens the settings UI" ) ;
[ button addTarget : self
action : @ selector ( settingsButtonPressed : )
forControlEvents : UIControlEventTouchUpInside ] ;
button . frame = CGRectMake ( 0 , 0 ,
round ( image . size . width + imageEdgeInsets . left + imageEdgeInsets . right ) ,
round ( image . size . height + imageEdgeInsets . top + imageEdgeInsets . bottom ) ) ;
self . navigationItem . leftBarButtonItem = [ [ UIBarButtonItem alloc ] initWithCustomView : button ] ;
}
}
- ( void ) settingsButtonPressed : ( id ) sender {
[ self performSegueWithIdentifier : @ "ShowAppSettingsSegue" sender : sender ] ;
2015-10-31 16:53:32 +01:00
}
2015-12-22 12:45:09 +01:00
- ( UIViewController * ) previewingContext : ( id < UIViewControllerPreviewing > ) previewingContext
2015-10-31 16:53:32 +01:00
viewControllerForLocation : ( CGPoint ) location {
2015-12-22 12:45:09 +01:00
NSIndexPath * indexPath = [ self . tableView indexPathForRowAtPoint : location ] ;
2015-12-26 17:27:27 +01:00
if ( indexPath ) {
[ previewingContext setSourceRect : [ self . tableView rectForRowAtIndexPath : indexPath ] ] ;
2016-12-01 21:46:08 +01:00
MessagesViewController * vc = [ MessagesViewController new ] ;
2015-12-26 17:27:27 +01:00
TSThread * thread = [ self threadForIndexPath : indexPath ] ;
2017-04-18 22:08:01 +02:00
[ vc configureForThread : thread keyboardOnViewAppearing : NO callOnViewAppearing : NO ] ;
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
}
2017-02-07 21:47:33 +01:00
- ( void ) handleActiveCallNotification : ( NSNotification * ) notification
{
AssertIsOnMainThread ( ) ;
2016-11-12 18:22:29 +01:00
if ( ! [ notification . object isKindOfClass : [ SignalCall class ] ] ) {
DDLogError ( @ "%@ expected presentCall observer to be notified with a SignalCall, but found %@" ,
2017-02-07 21:47:33 +01:00
self . tag ,
notification . object ) ;
2016-11-12 18:22:29 +01:00
return ;
}
2017-02-07 21:47:33 +01:00
2017-02-02 23:22:36 +01:00
SignalCall * call = ( SignalCall * ) notification . object ;
2017-02-02 16:55:14 +01:00
// Dismiss any other modals so we can present call modal .
if ( self . presentedViewController ) {
2017-03-22 17:49:26 +01:00
[ self dismissViewControllerAnimated : YES
2017-02-07 21:47:33 +01:00
completion : ^ {
2017-02-02 16:55:14 +01:00
[ self performSegueWithIdentifier : SignalsViewControllerSegueShowIncomingCall sender : call ] ;
} ] ;
} else {
[ self performSegueWithIdentifier : SignalsViewControllerSegueShowIncomingCall sender : call ] ;
}
2016-11-12 18:22:29 +01:00
}
2015-10-31 16:53:32 +01:00
- ( void ) previewingContext : ( id < UIViewControllerPreviewing > ) previewingContext
commitViewController : ( UIViewController * ) viewControllerToCommit {
2015-12-22 12:45:09 +01:00
MessagesViewController * vc = ( MessagesViewController * ) 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-05-04 15:50:31 +02:00
- ( IBAction ) composeNew
2017-05-02 00:06:53 +02:00
{
MessageComposeTableViewController * viewController = [ MessageComposeTableViewController new ] ;
2017-05-04 15:50:31 +02:00
UINavigationController * navigationController =
[ [ UINavigationController alloc ] initWithRootViewController : viewController ] ;
[ self presentTopLevelModalViewController : navigationController animateDismissal : YES animatePresentation : YES ] ;
2015-10-31 23:13:28 +01:00
}
2015-05-23 15:54:50 +02:00
- ( void ) swappedSegmentedControl {
if ( self . segmentedControl . selectedSegmentIndex = = 0 ) {
[ self selectedInbox : nil ] ;
} else {
[ self selectedArchive : nil ] ;
}
2014-11-24 21:51:43 +01:00
}
2015-12-22 12:45:09 +01:00
- ( void ) viewWillAppear : ( BOOL ) animated {
2014-11-24 21:51:43 +01:00
[ super viewWillAppear : animated ] ;
2015-12-22 12:45:09 +01:00
[ self checkIfEmptyView ] ;
2017-05-01 20:28:37 +02:00
if ( [ TSThread numberOfKeysInCollection ] > 0 ) {
[ self . contactsManager requestSystemContactsOnce ] ;
}
2016-04-01 17:34:47 +02:00
[ self updateInboxCountLabel ] ;
[ [ self tableView ] reloadData ] ;
}
- ( void ) viewDidAppear : ( BOOL ) animated {
[ super viewDidAppear : animated ] ;
2016-10-10 22:02:09 +02:00
if ( self . newlyRegisteredUser ) {
2017-02-27 17:04:14 +01:00
[ self . editingDbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * _Nonnull transaction ) {
[ self . experienceUpgradeFinder markAllAsSeenWithTransaction : transaction ] ;
} ] ;
2017-05-01 20:28:37 +02:00
[ self ensureNotificationsUpToDate ] ;
2017-02-27 17:04:14 +01:00
} else {
[ self displayAnyUnseenUpgradeExperience ] ;
2016-10-10 22:02:09 +02:00
}
}
2017-02-27 17:04:14 +01:00
# pragma mark - startup
- ( void ) displayAnyUnseenUpgradeExperience
{
AssertIsOnMainThread ( ) ;
__block NSArray < ExperienceUpgrade * > * unseenUpgrades ;
[ self . editingDbConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
unseenUpgrades = [ self . experienceUpgradeFinder allUnseenWithTransaction : transaction ] ;
} ] ;
if ( unseenUpgrades . count > 0 ) {
2017-03-02 21:11:44 +01:00
ExperienceUpgradesPageViewController * experienceUpgradeViewController = [ [ ExperienceUpgradesPageViewController alloc ] initWithExperienceUpgrades : unseenUpgrades ] ;
2017-02-27 17:04:14 +01:00
[ self presentViewController : experienceUpgradeViewController animated : YES completion : ^ {
[ self . editingDbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * _Nonnull transaction ) {
[ self . experienceUpgradeFinder markAllAsSeenWithTransaction : transaction ] ;
} ] ;
} ] ;
}
}
2016-10-10 22:02:09 +02:00
- ( void ) ensureNotificationsUpToDate
{
OWSSyncPushTokensJob * syncPushTokensJob =
[ [ OWSSyncPushTokensJob alloc ] initWithPushManager : [ PushManager sharedManager ]
2016-11-12 18:22:29 +01:00
accountManager : self . accountManager
2016-10-10 22:02:09 +02:00
preferences : [ Environment preferences ] ] ;
[ syncPushTokensJob run ] ;
}
2015-12-22 12:45:09 +01: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
}
2017-05-05 18:39:21 +02:00
- ( BOOL ) shouldShowMissingContactsPermissionView
{
if ( [ TSContactThread numberOfKeysInCollection ] = = 0 ) {
return NO ;
}
return ! self . contactsManager . isSystemContactsAuthorized ;
}
2015-01-24 04:26:04 +01:00
# pragma mark - Table View Data Source
2014-10-29 21:58:58 +01: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
}
- ( NSInteger ) tableView : ( UITableView * ) tableView numberOfRowsInSection : ( NSInteger ) section {
2014-11-21 14:38:37 +01:00
return ( NSInteger ) [ self . threadMappings numberOfItemsInSection : ( NSUInteger ) section ] ;
2014-10-29 21:58:58 +01:00
}
2017-05-05 18:39:21 +02:00
- ( UITableViewCell * ) tableView : ( UITableView * ) tableView cellForRowAtIndexPath : ( NSIndexPath * ) indexPath
{
2015-12-22 12:45:09 +01:00
InboxTableViewCell * cell =
[ self . tableView dequeueReusableCellWithIdentifier : NSStringFromClass ( [ InboxTableViewCell class ] ) ] ;
TSThread * thread = [ self threadForIndexPath : indexPath ] ;
2014-10-29 21:58:58 +01:00
if ( ! cell ) {
2015-12-26 17:27:27 +01:00
cell = [ InboxTableViewCell inboxTableViewCell ] ;
2014-10-29 21:58:58 +01:00
}
2015-12-22 12:45:09 +01:00
2017-04-08 16:47:47 +02:00
[ cell configureWithThread : thread contactsManager : self . contactsManager blockedPhoneNumberSet : _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
2015-12-22 12:45:09 +01:00
- ( TSThread * ) threadForIndexPath : ( NSIndexPath * ) indexPath {
2014-11-21 14:38:37 +01:00
__block TSThread * thread = nil ;
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
2015-12-22 12:45:09 +01: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
}
2014-11-21 14:38:37 +01:00
- ( CGFloat ) tableView : ( UITableView * ) tableView heightForRowAtIndexPath : ( NSIndexPath * ) indexPath {
return CELL_HEIGHT ;
}
2014-10-29 21:58:58 +01:00
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
forRowAtIndexPath : ( NSIndexPath * ) indexPath {
2015-01-14 22:30:01 +01:00
return ;
}
2015-01-27 21:17:49 +01:00
2015-12-22 12:45:09 +01:00
- ( NSArray * ) tableView : ( UITableView * ) tableView editActionsForRowAtIndexPath : ( NSIndexPath * ) indexPath {
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 ) {
[ self tableViewCellTappedDelete : swipedIndexPath ] ;
} ] ;
2015-12-26 17:27:27 +01:00
UITableViewRowAction * archiveAction ;
if ( self . viewingThreadsIn = = kInboxState ) {
archiveAction = [ UITableViewRowAction
rowActionWithStyle : UITableViewRowActionStyleNormal
2016-05-09 02:17:37 +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 ) {
[ self archiveIndexPath : tappedIndexPath ] ;
[ Environment . preferences setHasArchivedAMessage : YES ] ;
} ] ;
2015-12-22 12:45:09 +01:00
2015-12-26 17:27:27 +01:00
} else {
archiveAction = [ UITableViewRowAction
rowActionWithStyle : UITableViewRowActionStyleNormal
2016-05-09 02:17:37 +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 ) {
[ self archiveIndexPath : tappedIndexPath ] ;
} ] ;
}
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
}
- ( BOOL ) tableView : ( UITableView * ) tableView canEditRowAtIndexPath : ( NSIndexPath * ) indexPath {
return YES ;
}
2014-10-29 21:58:58 +01:00
# pragma mark - HomeFeedTableViewCellDelegate
2015-12-22 12:45:09 +01:00
- ( void ) tableViewCellTappedDelete : ( NSIndexPath * ) indexPath {
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
alertControllerWithTitle : [ NSString
stringWithFormat : NSLocalizedString ( @ "GROUP_REMOVING" , nil ) , [ thread name ] ]
message : nil
preferredStyle : UIAlertControllerStyleAlert ] ;
[ self presentViewController : removingFromGroup animated : YES completion : nil ] ;
TSOutgoingMessage * message = [ [ TSOutgoingMessage alloc ] initWithTimestamp : [ NSDate ows_millisecondTimeStamp ]
inThread : thread
2017-04-12 17:03:16 +02:00
groupMetaMessage : TSGroupMessageQuit ] ;
2016-11-15 11:15:51 +01:00
[ self . messageSender sendMessage : message
success : ^ {
[ self dismissViewControllerAnimated : YES
completion : ^ {
[ self deleteThread : thread ] ;
} ] ;
}
failure : ^ ( NSError * error ) {
[ self dismissViewControllerAnimated : YES
completion : ^ {
SignalAlertView ( NSLocalizedString ( @ "GROUP_REMOVING_FAILED" , nil ) ,
error . localizedRecoverySuggestion ) ;
} ] ;
} ] ;
} 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
}
- ( void ) deleteThread : ( TSThread * ) thread {
2014-12-05 23:38:13 +01:00
[ self . editingDbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2015-12-22 12:45:09 +01:00
[ thread removeWithTransaction : transaction ] ;
2014-12-05 23:38:13 +01:00
} ] ;
2015-12-26 17:27:27 +01:00
2015-01-14 22:30:01 +01:00
_inboxCount - = ( self . viewingThreadsIn = = kArchiveState ) ? 1 : 0 ;
2015-01-27 02:20:11 +01:00
[ self checkIfEmptyView ] ;
2014-10-29 21:58:58 +01:00
}
2015-12-26 17:27:27 +01:00
- ( void ) archiveIndexPath : ( NSIndexPath * ) indexPath {
TSThread * thread = [ self threadForIndexPath : indexPath ] ;
2015-12-22 12:45:09 +01:00
2015-07-10 15:00:14 +02:00
BOOL viewingThreadsIn = self . viewingThreadsIn ;
2014-12-05 23:38:13 +01:00
[ self . editingDbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2015-12-22 12:45:09 +01:00
viewingThreadsIn = = kInboxState ? [ thread archiveThreadWithTransaction : transaction ]
: [ thread unarchiveThreadWithTransaction : transaction ] ;
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
}
2015-12-22 12:45:09 +01:00
- ( NSNumber * ) updateInboxCountLabel {
2016-10-14 22:59:58 +02:00
NSUInteger numberOfItems = [ self . messagesManager unreadMessagesCount ] ;
2015-05-23 15:54:50 +02:00
NSNumber * badgeNumber = [ NSNumber numberWithUnsignedInteger : numberOfItems ] ;
NSString * unreadString = NSLocalizedString ( @ "WHISPER_NAV_BAR_TITLE" , nil ) ;
2015-12-22 12:45:09 +01:00
2015-02-10 12:02:58 +01:00
if ( ! [ badgeNumber isEqualToNumber : @ 0 ] ) {
2015-12-22 12:45:09 +01:00
NSString * badgeValue = [ badgeNumber stringValue ] ;
unreadString = [ unreadString stringByAppendingFormat : @ " (%@)" , badgeValue ] ;
2015-02-10 12:02:58 +01:00
}
2015-12-22 12:45:09 +01:00
2015-05-23 15:54:50 +02:00
[ _segmentedControl setTitle : unreadString forSegmentAtIndex : 0 ] ;
2017-02-07 22:29:23 +01:00
[ _segmentedControl . superview setNeedsLayout ] ;
2015-09-01 19:22:08 +02:00
[ _segmentedControl reloadInputViews ] ;
2015-04-14 21:49:00 +02:00
[ [ UIApplication sharedApplication ] setApplicationIconBadgeNumber : badgeNumber . integerValue ] ;
2015-12-22 12:45:09 +01:00
2015-05-23 15:54:50 +02:00
return badgeNumber ;
2014-10-29 21:58:58 +01:00
}
2015-12-22 12:45:09 +01:00
- ( void ) tableView : ( UITableView * ) tableView didSelectRowAtIndexPath : ( NSIndexPath * ) indexPath {
2015-12-26 17:27:27 +01:00
TSThread * thread = [ self threadForIndexPath : indexPath ] ;
2017-04-18 22:08:01 +02:00
[ self presentThread : thread keyboardOnViewAppearing : NO callOnViewAppearing : NO ] ;
2015-12-26 17:27:27 +01:00
[ tableView deselectRowAtIndexPath : indexPath animated : YES ] ;
}
2017-04-18 22:08:01 +02:00
- ( void ) presentThread : ( TSThread * ) thread
keyboardOnViewAppearing : ( BOOL ) keyboardOnViewAppearing
callOnViewAppearing : ( BOOL ) callOnViewAppearing
{
2015-12-26 17:27:27 +01:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2017-03-10 14:59:59 +01:00
MessagesViewController * mvc = [ [ MessagesViewController alloc ] initWithNibName : @ "MessagesViewController"
bundle : nil ] ;
2017-05-02 00:06:53 +02:00
[ mvc configureForThread : thread
keyboardOnViewAppearing : keyboardOnViewAppearing
callOnViewAppearing : callOnViewAppearing ] ;
2015-12-26 17:27:27 +01:00
2016-10-10 22:02:09 +02:00
if ( self . presentedViewController ) {
[ self . presentedViewController dismissViewControllerAnimated : YES completion : nil ] ;
}
[ self . navigationController popToRootViewControllerAnimated : YES ] ;
[ self . navigationController pushViewController : mvc animated : 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
{
OWSAssert ( [ NSThread isMainThread ] ) ;
OWSAssert ( viewController ) ;
[ self presentViewControllerWithBlock : ^ {
[ self presentViewController : viewController animated : animatePresentation completion : nil ] ;
}
animateDismissal : animateDismissal ] ;
}
- ( void ) pushTopLevelViewController : ( UIViewController * ) viewController
animateDismissal : ( BOOL ) animateDismissal
animatePresentation : ( BOOL ) animatePresentation
{
OWSAssert ( [ NSThread isMainThread ] ) ;
OWSAssert ( viewController ) ;
[ self presentViewControllerWithBlock : ^ {
[ self . navigationController pushViewController : viewController animated : animatePresentation ] ;
}
animateDismissal : animateDismissal ] ;
}
- ( void ) presentViewControllerWithBlock : ( void ( ^ ) ( ) ) presentationBlock animateDismissal : ( BOOL ) animateDismissal
{
OWSAssert ( [ NSThread isMainThread ] ) ;
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 .
void ( ^ dismissNavigationBlock ) ( ) = ^ {
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 ) {
[ self . presentedViewController dismissViewControllerAnimated : animateDismissal completion : dismissNavigationBlock ] ;
} else {
dismissNavigationBlock ( ) ;
}
}
2014-10-29 21:58:58 +01:00
# pragma mark - Navigation
- ( void ) prepareForSegue : ( UIStoryboardSegue * ) segue sender : ( id ) sender {
2017-03-23 14:55:39 +01:00
if ( [ segue . identifier isEqualToString : SignalsViewControllerSegueShowIncomingCall ] ) {
2016-11-12 18:22:29 +01:00
DDLogDebug ( @ "%@ preparing for incoming call segue" , self . tag ) ;
if ( ! [ segue . destinationViewController isKindOfClass : [ OWSCallViewController class ] ] ) {
DDLogError ( @ "%@ Received unexpected destination view controller: %@" , self . tag , segue . destinationViewController ) ;
return ;
}
OWSCallViewController * callViewController = ( OWSCallViewController * ) segue . destinationViewController ;
if ( ! [ sender isKindOfClass : [ SignalCall class ] ] ) {
DDLogError ( @ "%@ expecting call segueu to be sent by a SignalCall, but found: %@" , self . tag , sender ) ;
return ;
}
SignalCall * call = ( SignalCall * ) sender ;
TSContactThread * thread = [ TSContactThread getOrCreateThreadWithContactId : call . remotePhoneNumber ] ;
callViewController . thread = thread ;
callViewController . call = call ;
2014-10-29 21:58:58 +01:00
}
}
# pragma mark - IBAction
2015-12-22 12:45:09 +01:00
- ( IBAction ) selectedInbox : ( id ) sender {
2015-01-14 22:30:01 +01:00
self . viewingThreadsIn = kInboxState ;
[ self changeToGrouping : TSInboxGroup ] ;
}
2015-12-22 12:45:09 +01:00
- ( IBAction ) selectedArchive : ( id ) sender {
2015-01-14 22:30:01 +01:00
self . viewingThreadsIn = kArchiveState ;
[ self changeToGrouping : TSArchiveGroup ] ;
}
2015-12-22 12:45:09 +01:00
- ( void ) changeToGrouping : ( NSString * ) grouping {
self . threadMappings =
[ [ YapDatabaseViewMappings alloc ] initWithGroups : @ [ grouping ] view : TSThreadDatabaseViewExtensionName ] ;
2015-02-16 10:27:08 +01:00
[ self . threadMappings setIsReversed : YES forGroup : grouping ] ;
2015-12-22 12:45:09 +01:00
[ self . uiDatabaseConnection asyncReadWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
[ self . threadMappings updateWithTransaction : transaction ] ;
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ self . tableView reloadData ] ;
[ self checkIfEmptyView ] ;
} ) ;
2014-12-05 23:38:13 +01:00
} ] ;
2014-10-29 21:58:58 +01:00
}
2014-11-21 14:38:37 +01:00
# pragma mark Database delegates
- ( YapDatabaseConnection * ) uiDatabaseConnection {
NSAssert ( [ NSThread isMainThread ] , @ "Must access uiDatabaseConnection on main thread!" ) ;
if ( ! _uiDatabaseConnection ) {
YapDatabase * database = TSStorageManager . sharedManager . database ;
_uiDatabaseConnection = [ database newConnection ] ;
[ _uiDatabaseConnection beginLongLivedReadTransaction ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( yapDatabaseModified : )
name : YapDatabaseModifiedNotification
object : database ] ;
}
return _uiDatabaseConnection ;
}
- ( void ) yapDatabaseModified : ( NSNotification * ) notification {
2014-11-25 17:28:42 +01:00
NSArray * notifications = [ self . uiDatabaseConnection beginLongLivedReadTransaction ] ;
2014-11-21 14:38:37 +01:00
NSArray * sectionChanges = nil ;
2014-11-25 17:28:42 +01:00
NSArray * rowChanges = nil ;
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 .
if ( [ TSThread numberOfKeysInCollection ] > 0 ) {
[ self . contactsManager requestSystemContactsOnce ] ;
}
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
[ self updateInboxCountLabel ] ;
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
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 ) {
switch ( rowChange . type ) {
case YapDatabaseViewChangeDelete : {
2014-11-21 14:38:37 +01:00
[ self . tableView deleteRowsAtIndexPaths : @ [ rowChange . indexPath ]
withRowAnimation : UITableViewRowAnimationAutomatic ] ;
2015-01-14 22:30:01 +01:00
_inboxCount + = ( self . viewingThreadsIn = = kArchiveState ) ? 1 : 0 ;
2014-11-21 14:38:37 +01:00
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 ] ;
2015-01-14 22:30:01 +01:00
_inboxCount - = ( self . viewingThreadsIn = = kArchiveState ) ? 1 : 0 ;
2014-11-21 14:38:37 +01:00
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 ] ;
2015-01-27 02:20:11 +01:00
[ self checkIfEmptyView ] ;
2014-11-21 14:38:37 +01:00
}
2014-11-25 19:06:09 +01:00
2015-01-14 22:30:01 +01:00
- ( IBAction ) unwindSettingsDone : ( UIStoryboardSegue * ) segue {
}
- ( IBAction ) unwindMessagesView : ( UIStoryboardSegue * ) segue {
}
2015-12-22 12:45:09 +01:00
- ( void ) checkIfEmptyView {
2015-01-27 02:20:11 +01:00
[ _tableView setHidden : NO ] ;
2016-11-14 19:31:24 +01:00
[ _emptyBoxLabel setHidden : NO ] ;
2015-12-22 12:45:09 +01:00
if ( self . viewingThreadsIn = = kInboxState && [ self . threadMappings numberOfItemsInGroup : TSInboxGroup ] = = 0 ) {
2015-01-27 21:17:49 +01:00
[ self setEmptyBoxText ] ;
2015-01-27 02:20:11 +01:00
[ _tableView setHidden : YES ] ;
2015-12-22 12:45:09 +01:00
} else if ( self . viewingThreadsIn = = kArchiveState &&
[ self . threadMappings numberOfItemsInGroup : TSArchiveGroup ] = = 0 ) {
2015-01-27 21:17:49 +01:00
[ self setEmptyBoxText ] ;
2015-01-27 02:20:11 +01:00
[ _tableView setHidden : YES ] ;
2016-11-14 19:31:24 +01:00
} else {
[ _emptyBoxLabel setHidden : YES ] ;
2014-11-25 19:06:09 +01:00
}
2015-01-27 21:17:49 +01:00
}
2015-12-22 12:45:09 +01:00
- ( void ) setEmptyBoxText {
_emptyBoxLabel . textColor = [ UIColor grayColor ] ;
_emptyBoxLabel . font = [ UIFont ows_regularFontWithSize : 18. f ] ;
2015-01-27 21:17:49 +01:00
_emptyBoxLabel . textAlignment = NSTextAlignmentCenter ;
_emptyBoxLabel . numberOfLines = 4 ;
2015-12-22 12:45:09 +01:00
NSString * firstLine = @ "" ;
NSString * secondLine = @ "" ;
if ( self . viewingThreadsIn = = kInboxState ) {
if ( [ Environment . preferences getHasSentAMessage ] ) {
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 {
2016-08-10 15:58:34 +02:00
// FIXME This looks wrong . Shouldn ' t we be showing inbox_title / text here ?
2015-12-22 12:45:09 +01:00
firstLine = NSLocalizedString ( @ "EMPTY_ARCHIVE_FIRST_TITLE" , @ "" ) ;
2015-02-18 23:21:03 +01:00
secondLine = NSLocalizedString ( @ "EMPTY_ARCHIVE_FIRST_TEXT" , @ "" ) ;
2015-02-09 17:31:22 +01:00
}
2015-12-22 12:45:09 +01:00
} else {
if ( [ Environment . preferences getHasArchivedAMessage ] ) {
2016-08-10 15:58:34 +02:00
// FIXME This looks wrong . Shouldn ' t we be showing first_archive _title / text here ?
2015-12-22 12:45:09 +01:00
firstLine = NSLocalizedString ( @ "EMPTY_INBOX_TITLE" , @ "" ) ;
secondLine = NSLocalizedString ( @ "EMPTY_INBOX_TEXT" , @ "" ) ;
} else {
firstLine = NSLocalizedString ( @ "EMPTY_ARCHIVE_TITLE" , @ "" ) ;
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
value : [ UIColor blackColor ]
range : NSMakeRange ( 0 , firstLine . length ) ] ;
[ fullLabelString addAttribute : NSForegroundColorAttributeName
value : [ UIColor ows_darkGrayColor ]
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
}
2016-10-10 22:02:09 +02:00
# pragma mark - Logging
+ ( NSString * ) tag
{
return [ NSString stringWithFormat : @ "[%@]" , self . class ] ;
}
- ( NSString * ) tag
{
return self . class . tag ;
}
2014-10-29 21:58:58 +01:00
@ end