preferences: general

- remove clear history button
- add different type of notifications
- support notifications with actions

Change-Id: I05574cf11a22afdab4572daa6ba37602bab17eef
Reviewed-by: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
diff --git a/src/AppDelegate.mm b/src/AppDelegate.mm
index b051a6a..d6d43c7 100644
--- a/src/AppDelegate.mm
+++ b/src/AppDelegate.mm
@@ -20,18 +20,13 @@
 
 #import "AppDelegate.h"
 
+//lrc
 #import <callmodel.h>
-#import <qapplication.h>
-#import <accountmodel.h>
-#import <protocolmodel.h>
-#import <media/recordingmodel.h>
-#import <media/textrecording.h>
-#import <QItemSelectionModel>
-#import <QDebug>
-#import <account.h>
-#import <AvailableAccountModel.h>
 #import <api/lrc.h>
 #import <api/newaccountmodel.h>
+#import <api/behaviorcontroller.h>
+#import <api/conversation.h>
+#import <api/newcallmodel.h>
 
 
 #if ENABLE_SPARKLE
@@ -41,6 +36,7 @@
 #import "Constants.h"
 #import "RingWizardWC.h"
 #import "DialpadWC.h"
+#import "utils.h"
 
 #if ENABLE_SPARKLE
 @interface AppDelegate() <SUUpdaterDelegate>
@@ -57,6 +53,17 @@
 
 @end
 
+NSString * const MESSAGE_NOTIFICATION = @"message_notification_type";
+NSString * const CALL_NOTIFICATION = @"call_notification_type";
+NSString * const CONTACT_REQUEST_NOTIFICATION = @"contact_request_notification_type";
+
+NSString * const ACCOUNT_ID = @"account_id_notification_info";
+NSString * const CALL_ID = @"call_id_notification_info";
+NSString * const CONVERSATION_ID = @"conversation_id_notification_info";
+NSString * const CONTACT_URI = @"contact_uri_notification_info";
+NSString * const NOTIFICATION_TYPE = @"contact_type_notification_info";
+
+
 @implementation AppDelegate {
 
 std::unique_ptr<lrc::api::Lrc> lrc;
@@ -140,95 +147,165 @@
 
 - (void) connect
 {
-
-    //ProfileModel::instance().addCollection<LocalProfileCollection>(LoadOptions::FORCE_ENABLED);
-    QObject::connect(&AccountModel::instance(),
-                     &AccountModel::registrationChanged,
-                     [=](Account* a, bool registration) {
-                         qDebug() << "registrationChanged:" << a->id() << ":" << registration;
-                         //track buddy for account
-                         AccountModel::instance().subscribeToBuddies(a->id());
-                     });
-
-    QObject::connect(&CallModel::instance(),
-                     &CallModel::incomingCall,
-                     [=](Call* call) {
-                         // on incoming call set selected account match call destination account
-                         if (call->account()) {
-                             QModelIndex index = call->account()->index();
-                             index = AvailableAccountModel::instance().mapFromSource(index);
-
-                             AvailableAccountModel::instance().selectionModel()->setCurrentIndex(index,
-                                                                                                 QItemSelectionModel::ClearAndSelect);
-                         }
-                         BOOL shouldComeToForeground = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::WindowBehaviour];
-                         BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
-                         if (shouldComeToForeground) {
-                             [NSApp activateIgnoringOtherApps:YES];
-                             if ([self.ringWindowController.window isMiniaturized]) {
-                                 [self.ringWindowController.window deminiaturize:self];
-                             }
-                         }
-
-                         if(shouldNotify) {
-                             [self showIncomingNotification:call];
-                         }
-                     });
-
-    QObject::connect(&media::RecordingModel::instance(),
-                     &media::RecordingModel::newTextMessage,
-                     [=](media::TextRecording* t, ContactMethod* cm) {
-
-                         BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
-                         auto qIdx = t->instantTextMessagingModel()->index(t->instantTextMessagingModel()->rowCount()-1, 0);
-
-                         // Don't show a notification if we are sending the text OR window already has focus OR user disabled notifications
-                         if(qvariant_cast<media::Media::Direction>(qIdx.data((int)media::TextRecording::Role::Direction)) == media::Media::Direction::OUT
-                            || self.ringWindowController.window.isMainWindow || !shouldNotify)
+    QObject::connect(&lrc->getBehaviorController(),
+                     &lrc::api::BehaviorController::newTrustRequest,
+                     [self] (const std::string& accountId, const std::string& contactUri) {
+                         BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::ContactRequestNotifications];
+                         if(!shouldNotify) {
                              return;
+                         }
+                         NSUserNotification* notification = [[NSUserNotification alloc] init];
+                         auto contactModel = lrc->getAccountModel()
+                         .getAccountInfo(accountId).contactModel.get();
+                         NSString* name = contactModel->getContact(contactUri)
+                         .registeredName.empty() ?
+                         @(contactUri.c_str()) :
+                         @(contactModel->getContact(contactUri).registeredName.c_str());
+                         NSString* localizedMessage =
+                         NSLocalizedString(@"Send you a contact request",
+                                           @"Notification message");
 
+                         NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
+                         userInfo[ACCOUNT_ID] = @(accountId.c_str());
+                         userInfo[CONTACT_URI] = @(contactUri.c_str());
+                         userInfo[NOTIFICATION_TYPE] = CONTACT_REQUEST_NOTIFICATION;
+
+                         [notification setTitle: name];
+                         notification.informativeText = localizedMessage;
+                         [notification setSoundName:NSUserNotificationDefaultSoundName];
+                         [notification setUserInfo: userInfo];
+
+                         [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
+
+                     });
+
+    QObject::connect(&lrc->getBehaviorController(),
+                     &lrc::api::BehaviorController::showIncomingCallView,
+                     [self] (const std::string& accountId, lrc::api::conversation::Info conversationInfo) {
+                         BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::CallNotifications];
+                         if(!shouldNotify) {
+                             return;
+                         }
+                         NSString* name = bestIDForConversation(conversationInfo, *lrc->getAccountModel().getAccountInfo(accountId).conversationModel.get());
                          NSUserNotification* notification = [[NSUserNotification alloc] init];
 
-                         NSString* localizedTitle = [NSString stringWithFormat:NSLocalizedString(@"Message from %@", @"Message from {Name}"), qIdx.data((int)media::TextRecording::Role::AuthorDisplayname).toString().toNSString()];
+                         NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
+                         userInfo[ACCOUNT_ID] = @(accountId.c_str());
+                         userInfo[CALL_ID] = @(conversationInfo.callId.c_str());
+                         userInfo[CONVERSATION_ID] = @(conversationInfo.uid.c_str());
+                         userInfo[NOTIFICATION_TYPE] = CALL_NOTIFICATION;
+
+                         NSString* localizedTitle = [NSString stringWithFormat:
+                                                     NSLocalizedString(@"Incoming call from %@", @"Incoming call from {Name}"),
+                                                     name];
+                         // try to activate action button
+                         @try {
+                             [notification setValue:@YES forKey:@"_showsButtons"];
+                         }
+                         @catch (NSException *exception) {
+                             NSLog(@"Action button not activable on notification");
+                         }
+                         [notification setUserInfo: userInfo];
+                         [notification setOtherButtonTitle:NSLocalizedString(@"Refuse", @"Button Action")];
+                         [notification setActionButtonTitle:NSLocalizedString(@"Accept", @"Button Action")];
+                         [notification setTitle:localizedTitle];
+                         [notification setSoundName:NSUserNotificationDefaultSoundName];
+
+                         [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
+                     });
+
+    QObject::connect(&lrc->getBehaviorController(),
+                     &lrc::api::BehaviorController::newUnreadInteraction,
+                     [self] (const std::string& accountId, const std::string& conversation,
+                             uint64_t interactionId, const lrc::api::interaction::Info& interaction) {
+                         BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::MessagesNotifications];
+                         if(!shouldNotify) {
+                             return;
+                         }
+                         NSUserNotification* notification = [[NSUserNotification alloc] init];
+
+                         NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
+                         userInfo[ACCOUNT_ID] = @(accountId.c_str());
+                         userInfo[CONVERSATION_ID] = @(conversation.c_str());
+                         userInfo[NOTIFICATION_TYPE] = MESSAGE_NOTIFICATION;
+                         NSString* name = @(interaction.authorUri.c_str());
+                         auto convIt = getConversationFromUid(conversation, *lrc->getAccountModel().getAccountInfo(accountId).conversationModel.get());
+                         auto convQueue = lrc->getAccountModel().getAccountInfo(accountId).conversationModel.get()->allFilteredConversations();
+                         if (convIt != convQueue.end()) {
+                             name = bestIDForConversation(*convIt, *lrc->getAccountModel().getAccountInfo(accountId).conversationModel.get());
+                         }
+                         NSString* localizedTitle = [NSString stringWithFormat:
+                                                     NSLocalizedString(@"Incoming message from %@",@"Incoming message from {Name}"),
+                                                     name];
 
                          [notification setTitle:localizedTitle];
                          [notification setSoundName:NSUserNotificationDefaultSoundName];
-                         [notification setSubtitle:qIdx.data().toString().toNSString()];
+                         [notification setSubtitle:@(interaction.body.c_str())];
+                         [notification setUserInfo: userInfo];
 
                          [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
                      });
 }
 
-- (void) showIncomingNotification:(Call*) call{
-    NSUserNotification* notification = [[NSUserNotification alloc] init];
-    NSString* localizedTitle = [NSString stringWithFormat:
-                                NSLocalizedString(@"Incoming call from %@", @"Incoming call from {Name}"), call->peerName().toNSString()];
-    [notification setTitle:localizedTitle];
-    [notification setSoundName:NSUserNotificationDefaultSoundName];
-
-    // try to activate action button
-    @try {
-        [notification setValue:@YES forKey:@"_showsButtons"];
+- (void)userNotificationCenter:(NSUserNotificationCenter *)center didDismissAlert:(NSUserNotification *)alert {
+    // check if user click refuse on incoming call notifications
+    if(alert.activationType != NSUserNotificationActivationTypeNone) {
+        return;
     }
-    @catch (NSException *exception) {
-        // private API _showsButtons has changed...
-        NSLog(@"Action button not activable on notification");
-    }
-    [notification setActionButtonTitle:NSLocalizedString(@"Refuse", @"Button Action")];
 
-    [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
+    auto info = alert.userInfo;
+    if(!info) {
+        return;
+    }
+    NSString* identifier = info[NOTIFICATION_TYPE];
+    NSString* callId = info[CALL_ID];
+    NSString* accountId = info[ACCOUNT_ID];
+    if(!identifier || !callId || !accountId) {
+        return;
+    }
+    if([identifier isEqualToString: CALL_NOTIFICATION]) {
+        auto accountInfo = &lrc->getAccountModel().getAccountInfo([accountId UTF8String]);
+        if (accountInfo == nil)
+            return;
+        auto* callModel = accountInfo->callModel.get();
+        callModel->hangUp([callId UTF8String]);
+    }
 }
 
 - (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
 {
-    if(notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
-        CallModel::instance().selectedCall() << Call::Action::REFUSE;
-    } else {
-        [NSApp activateIgnoringOtherApps:YES];
-        if ([self.ringWindowController.window isMiniaturized]) {
+    auto info = notification.userInfo;
+    if(!info) {
+        return;
+    }
+    NSString* identifier = info[NOTIFICATION_TYPE];
+    if([identifier isEqualToString: CALL_NOTIFICATION]) {
+        if(notification.activationType == NSUserNotificationActivationTypeActionButtonClicked
+           || notification.activationType == NSUserNotificationActivationTypeContentsClicked) {
+            NSString* callId = info[CALL_ID];
+            NSString* accountId = info[ACCOUNT_ID];
+            NSString *conversationId = info[CONVERSATION_ID];
+            auto accountInfo = &lrc->getAccountModel().getAccountInfo([accountId UTF8String]);
+            if (accountInfo == nil)
+                return;
+            auto* callModel = accountInfo->callModel.get();
+            callModel->accept([callId UTF8String]);
             [self.ringWindowController.window deminiaturize:self];
+            [_ringWindowController showCall:callId forAccount:accountId forConversation:conversationId];
+        }
+    } else if(notification.activationType == NSUserNotificationActivationTypeContentsClicked) {
+        [self.ringWindowController.window deminiaturize:self];
+        if([identifier isEqualToString: MESSAGE_NOTIFICATION]) {
+            NSString* accountId = info[ACCOUNT_ID];
+            NSString *conversationId = info[CONVERSATION_ID];
+            [_ringWindowController showConversation:conversationId forAccount:accountId];
+        } else if([identifier isEqualToString: CONTACT_REQUEST_NOTIFICATION]) {
+            NSString* accountId = info[ACCOUNT_ID];
+            NSString *contactUri = info[CONTACT_URI];
+            [_ringWindowController showContactRequestFor:accountId contactUri: contactUri];
         }
     }
+    [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications];
 }
 
 - (void) showWizard
@@ -331,7 +408,6 @@
     }
     [self.wizard close];
     [self.ringWindowController close];
-    delete CallModel::instance().QObject::parent();
     [[NSApplication sharedApplication] terminate:self];
 }
 
diff --git a/src/ChooseAccountVC.h b/src/ChooseAccountVC.h
index 018cad7..3172445 100644
--- a/src/ChooseAccountVC.h
+++ b/src/ChooseAccountVC.h
@@ -42,11 +42,10 @@
 @property (retain, nonatomic) id <ChooseAccountDelegate> delegate;
 
 @property (readonly) const lrc::api::account::Info& selectedAccount;
-//@property const lrc::api::NewAccountModel* accMdl_;
 
 -(void) enable;
 -(void) disable;
-//-(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil model:(lrc::api::NewAccountModel*) accMdl delegate:(id <ChooseAccountDelegate> )mainWindow;
 -(void) updateWithDelegate:(id <ChooseAccountDelegate> )mainWindow andModel:(lrc::api::NewAccountModel*) accMdl;
+- (void)selectAccount:(NSString*)accountID;
 
 @end
diff --git a/src/ChooseAccountVC.mm b/src/ChooseAccountVC.mm
index 79df30c..a46550f 100644
--- a/src/ChooseAccountVC.mm
+++ b/src/ChooseAccountVC.mm
@@ -393,4 +393,14 @@
     [accountSelectionButton setEnabled:NO];
 }
 
+- (void)selectAccount:(NSString*)accountID {
+    auto accList = accMdl_->getAccountList();
+    if(std::find(accList.begin(), accList.end(), [accountID UTF8String]) != accList.end()) {
+        auto& account = accMdl_->getAccountInfo([accountID UTF8String]);
+        [accountSelectionManager_ setSavedAccount:account];
+        [self updatePhoto];
+        [self setPopUpButtonSelection];
+    }
+}
+
 @end
diff --git a/src/Constants.h b/src/Constants.h
index 808ef1b..c5987c1 100644
--- a/src/Constants.h
+++ b/src/Constants.h
@@ -30,7 +30,11 @@
     /* KVO compliant */
     NSString * const WindowBehaviour = @"window_behaviour";
     /* KVO compliant */
-    NSString * const Notifications = @"enable_notifications";
+    NSString * const CallNotifications = @"enable_call_notifications";
+    /* KVO compliant */
+    NSString * const MessagesNotifications = @"enable_messages_notifications";
+    /* KVO compliant */
+    NSString * const ContactRequestNotifications = @"enable_invitations_notifications";
     /* download folder for incoming images*/
     NSString * const DownloadFolder = @"download_folder";
 }
diff --git a/src/GeneralPrefsVC.mm b/src/GeneralPrefsVC.mm
index e706569..da78544 100644
--- a/src/GeneralPrefsVC.mm
+++ b/src/GeneralPrefsVC.mm
@@ -18,20 +18,7 @@
  */
 #import "GeneralPrefsVC.h"
 
-#import <Quartz/Quartz.h>
-
-//Qt
-#import <QSize>
-#import <QtMacExtras/qmacfunctions.h>
-#import <QPixmap>
-
-//LRC
-#import <categorizedhistorymodel.h>
-#import <profilemodel.h>
-#import <profile.h>
-#import <person.h>
-#import <globalinstances.h>
-#import <media/recordingmodel.h>
+//lrc
 #import <api/datatransfermodel.h>
 
 #if ENABLE_SPARKLE
@@ -39,22 +26,13 @@
 #endif
 
 #import "Constants.h"
-#import "views/NSImage+Extensions.h"
-#import "delegates/ImageManipulationDelegate.h"
 #import "utils.h"
 
 @interface GeneralPrefsVC () {
-    __unsafe_unretained IBOutlet NSTextField* historyChangedLabel;
     __unsafe_unretained IBOutlet NSButton* startUpButton;
     __unsafe_unretained IBOutlet NSButton* toggleAutomaticUpdateCheck;
     __unsafe_unretained IBOutlet NSPopUpButton* checkIntervalPopUp;
     __unsafe_unretained IBOutlet NSView* sparkleContainer;
-    __unsafe_unretained IBOutlet NSTextField* historyTextField;
-    __unsafe_unretained IBOutlet NSStepper* historyStepper;
-    __unsafe_unretained IBOutlet NSButton* historySwitch;
-    __unsafe_unretained IBOutlet NSButton* photoView;
-    __unsafe_unretained IBOutlet NSTextField* profileNameField;
-    __unsafe_unretained IBOutlet NSImageView* addProfilePhotoImage;
     __unsafe_unretained IBOutlet NSButton *downloadFolder;
     __unsafe_unretained IBOutlet NSTextField *downloadFolderLabel;
 }
@@ -77,18 +55,7 @@
 - (void)loadView
 {
     [super loadView];
-    [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:Preferences::HistoryLimit options:NSKeyValueObservingOptionNew context:NULL];
-
     [startUpButton setState:[self isLaunchAtStartup]];
-
-    int historyLimit = CategorizedHistoryModel::instance().historyLimit();
-    [historyTextField setStringValue:[NSString stringWithFormat:@"%d", historyLimit]];
-    [historyStepper setIntValue:historyLimit];
-
-    BOOL limited = CategorizedHistoryModel::instance().isHistoryLimited();
-    [historySwitch setState:limited];
-    [historyStepper setEnabled:limited];
-    [historyTextField setEnabled:limited];
 #if ENABLE_SPARKLE
     [sparkleContainer setHidden:NO];
     SUUpdater *updater = [SUUpdater sharedUpdater];
@@ -99,23 +66,6 @@
 #else
     [sparkleContainer setHidden:YES];
 #endif
-
-    [photoView setWantsLayer: YES];
-    photoView.layer.cornerRadius = photoView.frame.size.width / 2;
-    photoView.layer.masksToBounds = YES;
-    [addProfilePhotoImage setWantsLayer: YES];
-    [addProfilePhotoImage setHidden:NO];
-    [photoView setBordered:YES];
-
-    if (auto pro = ProfileModel::instance().selectedProfile()) {
-        [profileNameField setStringValue:pro->person()->formattedName().toNSString()];
-        if (pro->person() && pro->person()->photo().isValid()) {
-            auto photo = GlobalInstances::pixmapManipulator().contactPhoto(pro->person(), {140,140});
-            [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(photo))];
-            [addProfilePhotoImage setHidden:YES];
-            [photoView setBordered:NO];
-        }
-    }
     if (appSandboxed()) {
         [downloadFolder setHidden:YES];
         [downloadFolder setEnabled:NO];
@@ -127,27 +77,6 @@
     }
 }
 
-- (void) dealloc
-{
-    [[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:Preferences::HistoryLimit];
-}
-
-- (IBAction)clearHistory:(id)sender {
-    CategorizedHistoryModel::instance().clearAllCollections();
-    media::RecordingModel::instance().clearAllCollections();
-    [historyChangedLabel setHidden:NO];
-}
-
-- (IBAction)toggleHistory:(NSButton*)sender {
-    CategorizedHistoryModel::instance().setHistoryLimited([sender state]);
-    int historyLimit = CategorizedHistoryModel::instance().historyLimit();
-    [historyTextField setStringValue:[NSString stringWithFormat:@"%d", historyLimit]];
-    [historyStepper setIntValue:historyLimit];
-    [historyChangedLabel setHidden:NO];
-    [historyStepper setEnabled:[sender state]];
-    [historyTextField setEnabled:[sender state]];
-}
-
 - (IBAction)changeDownloadFolder:(id)sender {
 
     NSOpenPanel *panel = [NSOpenPanel openPanel];
@@ -163,16 +92,6 @@
     [[NSUserDefaults standardUserDefaults] setObject:path forKey:Preferences::DownloadFolder];
 }
 
-// KVO handler
--(void)observeValueForKeyPath:(NSString *)aKeyPath ofObject:(id)anObject
-                       change:(NSDictionary *)aChange context:(void *)aContext
-{
-    if ([aKeyPath isEqualToString:Preferences::HistoryLimit]) {
-        CategorizedHistoryModel::instance().setHistoryLimit([[aChange objectForKey: NSKeyValueChangeNewKey] integerValue]);
-        [historyChangedLabel setHidden:NO];
-    }
-}
-
 #pragma mark - Startup API
 
 // MIT license by Brian Dunagan
@@ -238,48 +157,6 @@
     return itemRef;
 }
 
-#pragma mark - Profile Photo edition
-
-- (IBAction) editPhoto:(id)sender {
-    auto pictureTaker = [IKPictureTaker pictureTaker];
-    [pictureTaker beginPictureTakerSheetForWindow:self.view.window
-                                     withDelegate:self
-                                   didEndSelector:@selector(pictureTakerDidEnd:returnCode:contextInfo:)
-                                      contextInfo:nil];
-}
-
-- (void) pictureTakerDidEnd:(IKPictureTaker *) picker
-                 returnCode:(NSInteger) code
-                contextInfo:(void*) contextInfo
-{
-    if (auto outputImage = [picker outputImage]) {
-        [photoView setImage:outputImage];
-        [addProfilePhotoImage setHidden:YES];
-        [photoView setBordered:NO];
-        if (auto pro = ProfileModel::instance().selectedProfile()) {
-            QPixmap p;
-            auto smallImg = [NSImage imageResize:outputImage newSize:{100,100}];
-            if (p.loadFromData(QByteArray::fromNSData([smallImg TIFFRepresentation]))) {
-                pro->person()->setPhoto(QVariant(p));
-            }
-            pro->save();
-        }
-    } else if (!photoView.image){
-        [addProfilePhotoImage setHidden:NO];
-        [photoView setBordered:YES];
-    }
-}
-
-#pragma mark - NSTextFieldDelegate methods
-
--(void)controlTextDidChange:(NSNotification *)notif
-{
-    if (auto pro = ProfileModel::instance().selectedProfile()) {
-        pro->person()->setFormattedName(profileNameField.stringValue.UTF8String);
-        pro->save();
-    }
-}
-
 #pragma mark - NSOpenSavePanelDelegate delegate methods
 
 - (BOOL) panel:(id)sender shouldEnableURL:(NSURL*)url {
diff --git a/src/RingWindowController.h b/src/RingWindowController.h
index 1d935b7..86c6b80 100644
--- a/src/RingWindowController.h
+++ b/src/RingWindowController.h
@@ -68,4 +68,8 @@
  */
 -(void) listTypeChanged;
 
+-(void) showConversation:(NSString* )conversationId forAccount:(NSString*)accountId;
+-(void) showCall:(NSString* )callId forAccount:(NSString*)accountId forConversation:(NSString*)conversationId;
+-(void) showContactRequestFor:(NSString* )accountId contactUri:(NSString*)uri;
+
 @end
diff --git a/src/RingWindowController.mm b/src/RingWindowController.mm
index 7a0a3f9..cbc82b7 100644
--- a/src/RingWindowController.mm
+++ b/src/RingWindowController.mm
@@ -535,4 +535,57 @@
     [smartViewVC reloadConversationWithUid:@(conversationID.c_str())];
 }
 
+-(void) showConversation:(NSString* )conversationId forAccount:(NSString*)accountId {
+    auto& accInfo = self.accountModel->getAccountInfo([accountId UTF8String]);
+    [chooseAccountVC selectAccount: accountId];
+    [settingsVC setSelectedAccount: [accountId UTF8String]];
+    [smartViewVC setConversationModel:accInfo.conversationModel.get()];
+    [smartViewVC selectConversationList];
+    [self updateRingID];
+    auto convInfo = getConversationFromUid([conversationId UTF8String], *accInfo.conversationModel.get());
+    auto convQueue = accInfo.conversationModel.get()->allFilteredConversations();
+    if (convInfo != convQueue.end()) {
+        [conversationVC setConversationUid:convInfo->uid model:accInfo.conversationModel.get()];
+        [smartViewVC selectConversation: *convInfo model:accInfo.conversationModel.get()];
+        accInfo.conversationModel.get()->clearUnreadInteractions([conversationId UTF8String]);
+    }
+    [self changeViewTo:SHOW_CONVERSATION_SCREEN];
+}
+
+-(void) showCall:(NSString* )callId forAccount:(NSString*)accountId forConversation:(NSString*)conversationId {
+    auto& accInfo = self.accountModel->getAccountInfo([accountId UTF8String]);
+    [chooseAccountVC selectAccount: accountId];
+    [settingsVC setSelectedAccount:accInfo.id];
+    [smartViewVC setConversationModel:accInfo.conversationModel.get()];
+    [self updateRingID];
+    auto convInfo = getConversationFromUid([conversationId UTF8String], *accInfo.conversationModel.get());
+    auto convQueue = accInfo.conversationModel.get()->allFilteredConversations();
+    if (convInfo != convQueue.end()) {
+        if (accInfo.contactModel->getContact(convInfo->participants[0]).profileInfo.type == lrc::api::profile::Type::PENDING)
+            [smartViewVC selectPendingList];
+        else
+            [smartViewVC selectConversationList];
+        [smartViewVC selectConversation: *convInfo model:accInfo.conversationModel.get()];
+    }
+    [currentCallVC setCurrentCall:[callId UTF8String]
+                     conversation:[conversationId UTF8String]
+                          account:&accInfo];
+    [self changeViewTo:SHOW_CALL_SCREEN];
+}
+
+-(void) showContactRequestFor:(NSString* )accountId contactUri:(NSString*)uri {
+    auto& accInfo = self.accountModel->getAccountInfo([accountId UTF8String]);
+    [chooseAccountVC selectAccount: accountId];
+    [settingsVC setSelectedAccount:accInfo.id];
+    [smartViewVC setConversationModel:accInfo.conversationModel.get()];
+    [self updateRingID];
+    [smartViewVC selectPendingList];
+    auto convInfo = getConversationFromURI([uri UTF8String], *accInfo.conversationModel.get());
+    auto convQueue = accInfo.conversationModel.get()->allFilteredConversations();
+    if (convInfo != convQueue.end()) {
+        [conversationVC setConversationUid:convInfo->uid model:accInfo.conversationModel.get()];
+        [smartViewVC selectConversation: *convInfo model:accInfo.conversationModel.get()];
+    }
+    [self changeViewTo:SHOW_CONVERSATION_SCREEN];
+}
 @end
diff --git a/src/RingWizardLinkAccountVC.mm b/src/RingWizardLinkAccountVC.mm
index 6697140..2239ba2 100644
--- a/src/RingWizardLinkAccountVC.mm
+++ b/src/RingWizardLinkAccountVC.mm
@@ -175,6 +175,7 @@
     }
 
     // enable Notifications
-    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:Preferences::Notifications];
+    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:Preferences::CallNotifications];
+    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:Preferences::MessagesNotifications];
 }
 @end
diff --git a/src/RingWizardNewAccountVC.mm b/src/RingWizardNewAccountVC.mm
index 493993c..9d709b2 100644
--- a/src/RingWizardNewAccountVC.mm
+++ b/src/RingWizardNewAccountVC.mm
@@ -321,7 +321,8 @@
     }
 
     // enable Notifications
-    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:Preferences::Notifications];
+    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:Preferences::CallNotifications];
+    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:Preferences::MessagesNotifications];
 }
 
 - (IBAction)cancel:(id)sender
diff --git a/src/utils.h b/src/utils.h
index ba01afb..f3cce1d 100755
--- a/src/utils.h
+++ b/src/utils.h
@@ -129,6 +129,19 @@
                         });
 }
 
+/**
+ * This function return an iterator pointing to a Conversation::Info in ConversationModel given its participant uri. Will not work for group chat.
+ * @param uri URI of participant
+ * @param model ConversationModel in which to do the lookup
+ * @return iterator pointing to corresponding Conversation if any. Points to past-the-end element otherwise.
+ */
+static inline lrc::api::ConversationModel::ConversationQueue::const_iterator getConversationFromURI(const std::string& uri, const lrc::api::ConversationModel& model) {
+    return std::find_if(model.allFilteredConversations().begin(), model.allFilteredConversations().end(),
+                        [&] (const lrc::api::conversation::Info& conv) {
+                            return uri == conv.participants[0];
+                        });
+}
+
 static inline void
 setVideoAutoQuality(bool autoQuality, std::string accountId)
 {
diff --git a/ui/Base.lproj/GeneralPrefs.strings b/ui/Base.lproj/GeneralPrefs.strings
index 134d36a..64e43ce 100644
--- a/ui/Base.lproj/GeneralPrefs.strings
+++ b/ui/Base.lproj/GeneralPrefs.strings
@@ -44,8 +44,8 @@
 /* Class = "NSTextFieldCell"; title = "Update"; ObjectID = "r4u-t3-gBc"; */
 "r4u-t3-gBc.title" = "Update";
 
-/* Class = "NSButtonCell"; title = "Enable Desktop Notifications"; ObjectID = "uCL-ye-tsv"; */
-"uCL-ye-tsv.title" = "Enable Desktop Notifications";
+/* Class = "NSButtonCell"; title = "Enable notifications for incoming calls"; ObjectID = "uCL-ye-tsv"; */
+"uCL-ye-tsv.title" = "Enable notifications for incoming calls";
 
 /* Class = "NSButtonCell"; title = "Bring Ring to foreground on incoming calls"; ObjectID = "uYI-hA-JHk"; */
 "uYI-hA-JHk.title" = "Bring Ring to foreground on incoming calls";
diff --git a/ui/Base.lproj/GeneralPrefs.xib b/ui/Base.lproj/GeneralPrefs.xib
index 7e16bf7..3b015d3 100644
--- a/ui/Base.lproj/GeneralPrefs.xib
+++ b/ui/Base.lproj/GeneralPrefs.xib
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
     <dependencies>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
@@ -10,10 +10,6 @@
                 <outlet property="checkIntervalPopUp" destination="RYP-3d-PCa" id="JNO-GR-CV8"/>
                 <outlet property="downloadFolder" destination="7bY-JW-z0U" id="ylD-Yb-I2N"/>
                 <outlet property="downloadFolderLabel" destination="IHD-1X-sid" id="XnP-ui-hIA"/>
-                <outlet property="historyChangedLabel" destination="Gyi-ID-Z3v" id="aoO-Fh-UCQ"/>
-                <outlet property="historyStepper" destination="QmA-ZI-ZL5" id="dDV-1G-rZs"/>
-                <outlet property="historySwitch" destination="DgD-2y-4g5" id="GYk-pz-jGT"/>
-                <outlet property="historyTextField" destination="tHZ-7Q-5iP" id="keP-y2-7Pg"/>
                 <outlet property="sparkleContainer" destination="yVO-jk-ay3" id="zni-hI-88D"/>
                 <outlet property="startUpButton" destination="1Nr-L4-fcd" id="veu-Hi-c7L"/>
                 <outlet property="toggleAutomaticUpdateCheck" destination="MCd-PD-kd7" id="rSB-ac-Nm2"/>
@@ -27,33 +23,48 @@
             <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
             <subviews>
                 <button translatesAutoresizingMaskIntoConstraints="NO" id="Oth-up-2k2">
-                    <rect key="frame" x="56" y="264" width="423" height="18"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="419" id="upW-bh-eQg"/>
-                    </constraints>
-                    <buttonCell key="cell" type="check" title="Enable Desktop Notifications" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="uCL-ye-tsv">
+                    <rect key="frame" x="38" y="264" width="252" height="18"/>
+                    <buttonCell key="cell" type="check" title="Enable notifications for incoming calls" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="uCL-ye-tsv">
                         <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                         <font key="font" metaFont="system"/>
                     </buttonCell>
                     <connections>
-                        <binding destination="Sz0-vm-i3t" name="value" keyPath="values.enable_notifications" id="PuD-KZ-Zat"/>
+                        <binding destination="Sz0-vm-i3t" name="value" keyPath="values.enable_call_notifications" id="ATK-nw-aIW"/>
                     </connections>
                 </button>
                 <button translatesAutoresizingMaskIntoConstraints="NO" id="1Nr-L4-fcd">
-                    <rect key="frame" x="56" y="210" width="423" height="18"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="419" id="8Mk-zb-NLA"/>
-                    </constraints>
+                    <rect key="frame" x="38" y="128" width="134" height="18"/>
                     <buttonCell key="cell" type="check" title="Launch on Startup" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="O2C-xR-mHF">
                         <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                         <font key="font" metaFont="system"/>
                     </buttonCell>
                     <connections>
                         <action selector="toggleLaunchAtStartup:" target="-2" id="Rky-YK-2yk"/>
+                        <binding destination="Sz0-vm-i3t" name="value" keyPath="values.window_behaviour" id="dve-QX-8LV"/>
+                    </connections>
+                </button>
+                <button translatesAutoresizingMaskIntoConstraints="NO" id="Esb-Tu-Qdy">
+                    <rect key="frame" x="38" y="230" width="285" height="18"/>
+                    <buttonCell key="cell" type="check" title="Enable notifications for incoming messages" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="0jV-Q4-232">
+                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                        <font key="font" metaFont="system"/>
+                    </buttonCell>
+                    <connections>
+                        <binding destination="Sz0-vm-i3t" name="value" keyPath="values.enable_messages_notifications" id="iOY-gd-hFa"/>
+                    </connections>
+                </button>
+                <button translatesAutoresizingMaskIntoConstraints="NO" id="amh-rw-Lc6">
+                    <rect key="frame" x="38" y="196" width="227" height="18"/>
+                    <buttonCell key="cell" type="check" title="Enable notifications for invitations" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="Lww-Wb-yrf">
+                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                        <font key="font" metaFont="system"/>
+                    </buttonCell>
+                    <connections>
+                        <binding destination="Sz0-vm-i3t" name="value" keyPath="values.enable_invitations_notifications" id="dc9-OG-CTZ"/>
                     </connections>
                 </button>
                 <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7bY-JW-z0U">
-                    <rect key="frame" x="171" y="166" width="82" height="32"/>
+                    <rect key="frame" x="141" y="84" width="82" height="32"/>
                     <constraints>
                         <constraint firstAttribute="width" relation="lessThanOrEqual" constant="150" id="d7f-Id-S7d"/>
                     </constraints>
@@ -66,80 +77,15 @@
                     </connections>
                 </button>
                 <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="IHD-1X-sid">
-                    <rect key="frame" x="56" y="175" width="103" height="17"/>
+                    <rect key="frame" x="38" y="93" width="103" height="17"/>
                     <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Download folder" id="6Ye-pe-9WV">
                         <font key="font" metaFont="system"/>
                         <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
                         <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                     </textFieldCell>
                 </textField>
-                <button translatesAutoresizingMaskIntoConstraints="NO" id="DgD-2y-4g5">
-                    <rect key="frame" x="56" y="125" width="140" height="18"/>
-                    <constraints>
-                        <constraint firstAttribute="width" relation="lessThanOrEqual" constant="215" id="DuR-wd-CxJ"/>
-                    </constraints>
-                    <buttonCell key="cell" type="check" title="Keep my history for" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="3Pb-Ec-zl5">
-                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
-                        <font key="font" metaFont="system"/>
-                    </buttonCell>
-                    <connections>
-                        <action selector="toggleHistory:" target="-2" id="2wV-yt-1YG"/>
-                    </connections>
-                </button>
-                <stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QmA-ZI-ZL5">
-                    <rect key="frame" x="247" y="120" width="19" height="27"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="13" id="o2I-Rn-0uA"/>
-                    </constraints>
-                    <stepperCell key="cell" continuous="YES" alignment="left" maxValue="100" doubleValue="30" id="30B-YQ-Opa"/>
-                    <connections>
-                        <binding destination="Sz0-vm-i3t" name="value" keyPath="values.history_limit" id="c2j-mK-kYa"/>
-                    </connections>
-                </stepper>
-                <textField verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tHZ-7Q-5iP">
-                    <rect key="frame" x="202" y="123" width="40" height="22"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="40" id="Qal-Za-gWz"/>
-                    </constraints>
-                    <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" alignment="center" title="30" drawsBackground="YES" id="JvS-c4-OeT">
-                        <font key="font" metaFont="system"/>
-                        <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
-                        <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
-                    </textFieldCell>
-                    <connections>
-                        <binding destination="Sz0-vm-i3t" name="value" keyPath="values.history_limit" id="4us-N3-vCf">
-                            <dictionary key="options">
-                                <bool key="NSContinuouslyUpdatesValue" value="YES"/>
-                            </dictionary>
-                        </binding>
-                    </connections>
-                </textField>
-                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nah-Jm-ZYB">
-                    <rect key="frame" x="269" y="125" width="33" height="17"/>
-                    <constraints>
-                        <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="29" id="08C-IP-5pg"/>
-                    </constraints>
-                    <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="days" id="e5K-l0-Nfw">
-                        <font key="font" metaFont="system"/>
-                        <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
-                        <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
-                    </textFieldCell>
-                </textField>
-                <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="U84-le-Iy4">
-                    <rect key="frame" x="58" y="93" width="122" height="19"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="122" id="3MQ-xB-seJ"/>
-                    </constraints>
-                    <buttonCell key="cell" type="roundRect" title="Clear History" bezelStyle="roundedRect" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="cd5-9L-Bbb">
-                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
-                        <font key="font" metaFont="cellTitle"/>
-                    </buttonCell>
-                    <connections>
-                        <action selector="clearHistory:" target="-2" id="yN7-bB-7EY"/>
-                    </connections>
-                </button>
                 <customView translatesAutoresizingMaskIntoConstraints="NO" id="yVO-jk-ay3">
-                    <rect key="frame" x="20" y="20" width="462" height="61"/>
+                    <rect key="frame" x="20" y="12" width="462" height="61"/>
                     <subviews>
                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="diX-uH-Ce8">
                             <rect key="frame" x="18" y="44" width="52" height="17"/>
@@ -160,7 +106,7 @@
                             </buttonCell>
                         </button>
                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="RYP-3d-PCa" userLabel="Update interval">
-                            <rect key="frame" x="264" y="12" width="100" height="26"/>
+                            <rect key="frame" x="264" y="12" width="100" height="25"/>
                             <constraints>
                                 <constraint firstAttribute="width" constant="95" id="oLq-z9-b9M"/>
                             </constraints>
@@ -189,26 +135,8 @@
                         <constraint firstItem="diX-uH-Ce8" firstAttribute="top" secondItem="yVO-jk-ay3" secondAttribute="top" id="uF2-Tp-YBe"/>
                     </constraints>
                 </customView>
-                <textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Gyi-ID-Z3v">
-                    <rect key="frame" x="306" y="127" width="172" height="14"/>
-                    <textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" title="(Applied on application restart)" id="OTl-vx-S43">
-                        <font key="font" metaFont="smallSystem"/>
-                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
-                        <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
-                    </textFieldCell>
-                </textField>
-                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fC0-ce-Yiz">
-                    <rect key="frame" x="18" y="147" width="399" height="8"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="395" id="6HO-p7-tAk"/>
-                        <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="48" id="JrN-bM-7Wz"/>
-                    </constraints>
-                </textField>
                 <button translatesAutoresizingMaskIntoConstraints="NO" id="Is4-pD-LOT">
-                    <rect key="frame" x="56" y="237" width="423" height="18"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="419" id="iUi-lt-J9M"/>
-                    </constraints>
+                    <rect key="frame" x="38" y="162" width="280" height="18"/>
                     <buttonCell key="cell" type="check" title="Bring Ring to foreground on incoming calls" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="uYI-hA-JHk">
                         <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                         <font key="font" metaFont="system"/>
@@ -219,37 +147,25 @@
                 </button>
             </subviews>
             <constraints>
-                <constraint firstItem="QmA-ZI-ZL5" firstAttribute="centerY" secondItem="nah-Jm-ZYB" secondAttribute="centerY" id="2CN-A5-Lfs"/>
-                <constraint firstItem="fC0-ce-Yiz" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" constant="20" id="2iV-DB-Zna"/>
-                <constraint firstItem="1Nr-L4-fcd" firstAttribute="leading" secondItem="DgD-2y-4g5" secondAttribute="leading" id="3wo-TW-rpe"/>
-                <constraint firstItem="DgD-2y-4g5" firstAttribute="baseline" secondItem="tHZ-7Q-5iP" secondAttribute="baseline" id="9yS-UP-Xii"/>
-                <constraint firstItem="Gyi-ID-Z3v" firstAttribute="leading" secondItem="nah-Jm-ZYB" secondAttribute="trailing" constant="8" id="EYY-WC-fCM"/>
                 <constraint firstItem="Oth-up-2k2" firstAttribute="leading" secondItem="Is4-pD-LOT" secondAttribute="leading" id="Fdb-2p-ULe"/>
-                <constraint firstItem="1Nr-L4-fcd" firstAttribute="top" secondItem="Is4-pD-LOT" secondAttribute="bottom" constant="13" id="IQl-Me-RRm"/>
-                <constraint firstItem="nah-Jm-ZYB" firstAttribute="leading" secondItem="QmA-ZI-ZL5" secondAttribute="trailing" constant="8" id="JJi-0O-nUi"/>
-                <constraint firstItem="tHZ-7Q-5iP" firstAttribute="top" secondItem="QmA-ZI-ZL5" secondAttribute="top" id="Jrk-f0-97K"/>
-                <constraint firstItem="7bY-JW-z0U" firstAttribute="leading" secondItem="IHD-1X-sid" secondAttribute="trailing" constant="20" id="Ptz-be-9mW"/>
-                <constraint firstItem="Gyi-ID-Z3v" firstAttribute="centerY" secondItem="nah-Jm-ZYB" secondAttribute="centerY" id="VGH-9C-eD1"/>
+                <constraint firstItem="1Nr-L4-fcd" firstAttribute="top" secondItem="Is4-pD-LOT" secondAttribute="bottom" constant="20" id="IQl-Me-RRm"/>
+                <constraint firstItem="Oth-up-2k2" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" constant="40" id="L3i-yX-aR1"/>
+                <constraint firstItem="Is4-pD-LOT" firstAttribute="top" secondItem="amh-rw-Lc6" secondAttribute="bottom" constant="20" id="Mhj-qi-2kU"/>
+                <constraint firstItem="amh-rw-Lc6" firstAttribute="leading" secondItem="Oth-up-2k2" secondAttribute="leading" id="Onb-2V-oDj"/>
+                <constraint firstItem="IHD-1X-sid" firstAttribute="leading" secondItem="1Nr-L4-fcd" secondAttribute="leading" id="ROL-JN-PCH"/>
+                <constraint firstItem="Esb-Tu-Qdy" firstAttribute="top" secondItem="Oth-up-2k2" secondAttribute="bottom" constant="20" id="SdP-aO-0F7"/>
+                <constraint firstItem="Esb-Tu-Qdy" firstAttribute="leading" secondItem="Oth-up-2k2" secondAttribute="leading" id="TOO-U1-1YT"/>
                 <constraint firstItem="IHD-1X-sid" firstAttribute="top" secondItem="1Nr-L4-fcd" secondAttribute="bottom" constant="20" id="X4c-Ad-8yh"/>
-                <constraint firstItem="QmA-ZI-ZL5" firstAttribute="leading" secondItem="tHZ-7Q-5iP" secondAttribute="trailing" constant="8" id="YPs-UE-IFi"/>
-                <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Gyi-ID-Z3v" secondAttribute="trailing" constant="20" id="Z4C-x5-1Op"/>
                 <constraint firstItem="7bY-JW-z0U" firstAttribute="centerY" secondItem="IHD-1X-sid" secondAttribute="centerY" id="aI1-uX-lQY"/>
-                <constraint firstItem="DgD-2y-4g5" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" constant="58" id="aPM-dp-Eym"/>
-                <constraint firstAttribute="bottom" secondItem="yVO-jk-ay3" secondAttribute="bottom" constant="20" id="bAA-rc-QhB"/>
-                <constraint firstItem="tHZ-7Q-5iP" firstAttribute="leading" secondItem="DgD-2y-4g5" secondAttribute="trailing" constant="8" id="bV5-ZQ-94d"/>
                 <constraint firstItem="Oth-up-2k2" firstAttribute="leading" secondItem="1Nr-L4-fcd" secondAttribute="leading" id="csu-Ug-9sD"/>
                 <constraint firstItem="Oth-up-2k2" firstAttribute="top" secondItem="c22-O7-iKe" secondAttribute="top" constant="20" id="dA1-zE-Xe8"/>
-                <constraint firstItem="DgD-2y-4g5" firstAttribute="leading" secondItem="U84-le-Iy4" secondAttribute="leading" id="ggy-GJ-WMj"/>
-                <constraint firstItem="DgD-2y-4g5" firstAttribute="leading" secondItem="IHD-1X-sid" secondAttribute="leading" id="hIj-5P-1iF"/>
-                <constraint firstItem="Is4-pD-LOT" firstAttribute="top" secondItem="Oth-up-2k2" secondAttribute="bottom" constant="13" id="lHy-u9-IB0"/>
+                <constraint firstItem="yVO-jk-ay3" firstAttribute="top" secondItem="IHD-1X-sid" secondAttribute="bottom" constant="20" id="pNA-q8-Kbk"/>
                 <constraint firstItem="yVO-jk-ay3" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" constant="20" id="qa3-OH-fvn"/>
-                <constraint firstItem="U84-le-Iy4" firstAttribute="top" secondItem="DgD-2y-4g5" secondAttribute="bottom" constant="15" id="szl-Nh-CbR"/>
-                <constraint firstItem="fC0-ce-Yiz" firstAttribute="top" secondItem="IHD-1X-sid" secondAttribute="bottom" constant="20" id="vNW-gf-432"/>
-                <constraint firstItem="yVO-jk-ay3" firstAttribute="top" secondItem="U84-le-Iy4" secondAttribute="bottom" constant="13" id="wfT-QL-241"/>
+                <constraint firstItem="amh-rw-Lc6" firstAttribute="top" secondItem="Esb-Tu-Qdy" secondAttribute="bottom" constant="20" id="tcE-4N-wrm"/>
                 <constraint firstAttribute="trailing" secondItem="yVO-jk-ay3" secondAttribute="trailing" constant="20" id="wig-B4-OtV"/>
-                <constraint firstItem="tHZ-7Q-5iP" firstAttribute="top" secondItem="fC0-ce-Yiz" secondAttribute="bottom" constant="2" id="wo5-OT-LTj"/>
+                <constraint firstItem="7bY-JW-z0U" firstAttribute="leading" secondItem="IHD-1X-sid" secondAttribute="trailing" constant="8" id="wji-Ac-kwB"/>
             </constraints>
-            <point key="canvasLocation" x="312.5" y="81.5"/>
+            <point key="canvasLocation" x="312" y="81"/>
         </customView>
         <userDefaultsController representsSharedInstance="YES" id="Sz0-vm-i3t"/>
         <customObject id="VEJ-ic-3Ub" customClass="SUUpdater"/>
diff --git a/ui/Base.lproj/Localizable.strings b/ui/Base.lproj/Localizable.strings
index 3b20c44..04c0cdf 100644
--- a/ui/Base.lproj/Localizable.strings
+++ b/ui/Base.lproj/Localizable.strings
@@ -85,6 +85,9 @@
 /* Incoming call from {Name} */
 "Incoming call from %@" = "Incoming call from %@";
 
+/* Incoming message from {Name} */
+"Incoming message from %@" = "Incoming message from %@";
+
 /* Message from {Name} */
 "Message from %@" = "Message from %@";
 
@@ -114,6 +117,7 @@
 
 /* Button Action */
 "Refuse" = "Refuse";
+"Accept" = "Accept";
 
 /* Contextual menu entry */
 "Remove value" = "Remove value";
@@ -183,3 +187,6 @@
 "Create a new account" = "Create a new account";
 "Add a SIP account" = "Add a SIP account";
 
+/* Notification message */
+"Send you a contact request" = "Send you a contact request";
+