From bdff3fba296c805f2a1d6d0bb10891b6a764471b Mon Sep 17 00:00:00 2001
From: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
Date: Mon, 27 Jul 2020 09:04:40 -0400
Subject: [PATCH] conversations: fix vCard loading

- catch exceptions that could be thrown on ios 12 during contact
serialization
- add conversation to smart list even if profile loading failed

Change-Id: I2c2f6cff9870331505e3055f1f42e1594e622a25
Gitlab: #86
---
 Ring/Ring.xcodeproj/project.pbxproj           |  6 ++++
 Ring/Ring/Bridging/ObjCHandler.h              | 31 ++++++++++++++++
 Ring/Ring/Bridging/ObjCHandler.m              | 35 +++++++++++++++++++
 Ring/Ring/Bridging/Ring-Bridging-Header.h     |  1 +
 Ring/Ring/Database/DBManager.swift            | 19 +++++-----
 .../CNContactVCardSerialization+Helpers.swift | 27 +++++++-------
 .../SmartList/SmartlistViewModel.swift        | 10 +++---
 Ring/Ring/Features/Me/Me/AccountHeader.xib    | 11 +++---
 8 files changed, 109 insertions(+), 31 deletions(-)
 create mode 100644 Ring/Ring/Bridging/ObjCHandler.h
 create mode 100644 Ring/Ring/Bridging/ObjCHandler.m

diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj
index f24f42adb..210245ac9 100644
--- a/Ring/Ring.xcodeproj/project.pbxproj
+++ b/Ring/Ring.xcodeproj/project.pbxproj
@@ -247,6 +247,7 @@
 		1ABE07DC1F0D915100D36361 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1ABE07DA1F0D915100D36361 /* Localizable.strings */; };
 		1ABE07DF1F0D91A800D36361 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1ABE07DD1F0D91A800D36361 /* LaunchScreen.storyboard */; };
 		1ABE07E21F0D924700D36361 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABE07E11F0D924700D36361 /* Strings.swift */; };
+		26069B6724C9FCE8002361A3 /* ObjCHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 26069B6624C9FCE8002361A3 /* ObjCHandler.m */; };
 		263B7158246D9390007044C4 /* SmartListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B7157246D9390007044C4 /* SmartListCell.swift */; };
 		263B715A246D9556007044C4 /* IncognitoSmartListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B7159246D9556007044C4 /* IncognitoSmartListCell.swift */; };
 		263B715C246D96E5007044C4 /* IncognitoSmartListCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 263B715B246D96E5007044C4 /* IncognitoSmartListCell.xib */; };
@@ -640,6 +641,8 @@
 		1ABE07DB1F0D915100D36361 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
 		1ABE07DD1F0D91A800D36361 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = Resources/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		1ABE07E11F0D924700D36361 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
+		26069B6524C9FCE8002361A3 /* ObjCHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjCHandler.h; sourceTree = "<group>"; };
+		26069B6624C9FCE8002361A3 /* ObjCHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjCHandler.m; sourceTree = "<group>"; };
 		26376721245315E600CDC51F /* Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Debug.entitlements; sourceTree = "<group>"; };
 		263B7157246D9390007044C4 /* SmartListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartListCell.swift; sourceTree = "<group>"; };
 		263B7159246D9556007044C4 /* IncognitoSmartListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoSmartListCell.swift; sourceTree = "<group>"; };
@@ -1010,6 +1013,8 @@
 				62AF6863201A66F0003AA9E8 /* AudioAdapter.h */,
 				62B60AF72048A35B001BEACF /* DataTransferAdapter.h */,
 				62B60AF82048A40D001BEACF /* DataTransferAdapter.mm */,
+				26069B6524C9FCE8002361A3 /* ObjCHandler.h */,
+				26069B6624C9FCE8002361A3 /* ObjCHandler.m */,
 			);
 			path = Bridging;
 			sourceTree = "<group>";
@@ -2134,6 +2139,7 @@
 				1A2D18C51F29180700B2C785 /* ContactModel.swift in Sources */,
 				0E3697A8203243D3009A68CA /* BannedContactCell.swift in Sources */,
 				1A2D18F71F292D7200B2C785 /* MessageCellSent.swift in Sources */,
+				26069B6724C9FCE8002361A3 /* ObjCHandler.m in Sources */,
 				446FAF1B2373425E00519C4F /* SendFileViewController.swift in Sources */,
 				04399AAC1D1C304300E99CD9 /* AccountAdapter.mm in Sources */,
 				0E68571120238546008B0717 /* ConversationNavigation.swift in Sources */,
diff --git a/Ring/Ring/Bridging/ObjCHandler.h b/Ring/Ring/Bridging/ObjCHandler.h
new file mode 100644
index 000000000..5bfaf212e
--- /dev/null
+++ b/Ring/Ring/Bridging/ObjCHandler.h
@@ -0,0 +1,31 @@
+/*
+*  Copyright (C) 2020 Savoir-faire Linux Inc.
+*
+*  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+*
+*  This program is free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  You should have received a copy of the GNU General Public License
+*  along with this program; if not, write to the Free Software
+*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+*/
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ObjCHandler : NSObject
+
++ (BOOL)tryBlock:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Ring/Ring/Bridging/ObjCHandler.m b/Ring/Ring/Bridging/ObjCHandler.m
new file mode 100644
index 000000000..9dc76353b
--- /dev/null
+++ b/Ring/Ring/Bridging/ObjCHandler.m
@@ -0,0 +1,35 @@
+/*
+*  Copyright (C) 2020 Savoir-faire Linux Inc.
+*
+*  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+*
+*  This program is free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  You should have received a copy of the GNU General Public License
+*  along with this program; if not, write to the Free Software
+*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+*/
+
+#import "ObjCHandler.h"
+
+@implementation ObjCHandler
+
++ (BOOL)tryBlock:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error {
+    @try {
+        tryBlock();
+        return YES;
+    } @catch (NSException *exception) {
+        *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo];
+        return NO;
+    }
+}
+
+@end
diff --git a/Ring/Ring/Bridging/Ring-Bridging-Header.h b/Ring/Ring/Bridging/Ring-Bridging-Header.h
index 482aec9e8..e2316214e 100644
--- a/Ring/Ring/Bridging/Ring-Bridging-Header.h
+++ b/Ring/Ring/Bridging/Ring-Bridging-Header.h
@@ -45,3 +45,4 @@
 #import <GSKStretchyHeaderView/GSKStretchyHeaderView.h>
 #import "DataTransferAdapter.h"
 #import "../../WhirlyGlobeMaply/ios/library/WhirlyGlobe-MaplyComponent/include/MaplyBridge.h"
+#import "ObjCHandler.h"
diff --git a/Ring/Ring/Database/DBManager.swift b/Ring/Ring/Database/DBManager.swift
index 35ca45337..beb5cba9c 100644
--- a/Ring/Ring/Database/DBManager.swift
+++ b/Ring/Ring/Database/DBManager.swift
@@ -605,17 +605,16 @@ class DBManager {
         for conversationID in conversations.map({ $0.id }) {
             guard let participants = try self.getParticipantsForConversation(conversationID: conversationID,
                 dataBase: dataBase),
-                let partisipant = participants.first else {
+                let participant = participants.first else {
                     continue
             }
-            guard let participantProfile = try self.getProfile(for: partisipant, createIfNotExists: false, accountId: accountId) else {
-                continue
-            }
-            let type = participantProfile.uri.contains("ring:") ? URIType.ring : URIType.sip
-            let uri = JamiURI.init(schema: type, infoHach: participantProfile.uri)
+            let type = participant.contains("ring:") ? URIType.ring : URIType.sip
+            let uri = JamiURI.init(schema: type, infoHach: participant)
             let conversationModel = ConversationModel(withParticipantUri: uri,
                                                       accountId: accountId)
-            conversationModel.participantProfile = participantProfile
+            if let participantProfile = try self.getProfile(for: participant, createIfNotExists: false, accountId: accountId) {
+                conversationModel.participantProfile = participantProfile
+            }
             conversationModel.conversationId = String(conversationID)
             var messages = [MessageModel]()
             guard let interactions = try self.interactionHepler
@@ -625,8 +624,8 @@ class DBManager {
                         continue
             }
             for interaction in interactions {
-                let author = interaction.author == participantProfile.uri
-                    ? participantProfile.uri : ""
+                let author = interaction.author == participant
+                    ? participant : ""
                 if let message = self.convertToMessage(interaction: interaction, author: author) {
                     messages.append(message)
                     let displayedMessage = author.isEmpty && message.status == .displayed
@@ -778,7 +777,9 @@ class DBManager {
             return nil
         }
         let conversationID = Int64(arc4random_uniform(10000000))
+        do {
         _ = try self.getProfile(for: contactUri, createIfNotExists: true, accountId: accountId)
+        } catch {}
         let conversationForContact = Conversation(conversationID, contactUri)
         if !self.conversationHelper.insert(item: conversationForContact, dataBase: dataBase) {
             return nil
diff --git a/Ring/Ring/Extensions/CNContactVCardSerialization+Helpers.swift b/Ring/Ring/Extensions/CNContactVCardSerialization+Helpers.swift
index c2f4dbb56..8e5258045 100644
--- a/Ring/Ring/Extensions/CNContactVCardSerialization+Helpers.swift
+++ b/Ring/Ring/Extensions/CNContactVCardSerialization+Helpers.swift
@@ -88,23 +88,24 @@ extension CNContactVCardSerialization {
     }
 
     class func parseToVCard(data: Data) -> CNContact? {
+        var contact: CNContact?
         do {
-            let vCards = try CNContactVCardSerialization.contacts(with: data)
-            guard let vCard = vCards.first else { return nil }
-            if let returnData = String(data: data, encoding: .utf8) {
+            try ObjCHandler.try {
+                guard let vCards = try? CNContactVCardSerialization.contacts(with: data),
+                    let vCard = vCards.first,
+                    let returnData = String(data: data, encoding: .utf8) else { return }
                 let contentArr = returnData.components(separatedBy: "\n")
-                if let nameRow = contentArr.filter({ String($0.prefix(3)) == VCardFields.fullName.rawValue }).first {
-                    let vcard = CNMutableContact()
-                    let name = String(nameRow.suffix(nameRow.count - 3))
-                    vcard.familyName = name
-                    vcard.phoneNumbers = vCard.phoneNumbers
-                    vcard.imageData = vCard.imageData
-                    return vcard
-                }
+                guard let nameRow = contentArr.filter({ String($0.prefix(3)) == VCardFields.fullName.rawValue }).first else { return }
+                let vcard = CNMutableContact()
+                let name = String(nameRow.suffix(nameRow.count - 3))
+                vcard.familyName = name
+                vcard.phoneNumbers = vCard.phoneNumbers
+                vcard.imageData = vCard.imageData
+                contact = vcard
             }
-            return vCard
         } catch {
-            return nil
+            print("An error ocurred during CNContactVCardSerialization: \(error)")
         }
+        return contact
     }
 }
diff --git a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift
index 2abcbf1c8..04b36d910 100644
--- a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift
+++ b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift
@@ -117,7 +117,7 @@ class SmartlistViewModel: Stateable, ViewModel, FilterConversationDataSource {
                 guard let name = profile.alias else { return UIImage.defaultJamiAvatarFor(profileName: nil, account: account) }
                 let profileName = name.isEmpty ? nil : name
                 return UIImage.defaultJamiAvatarFor(profileName: profileName, account: account)
-            })
+            }).startWith(UIImage(asset: Asset.icContactPicture)!)
         }()
 
     lazy var conversations: Observable<[ConversationSection]> = { [unowned self] in
@@ -174,13 +174,13 @@ class SmartlistViewModel: Stateable, ViewModel, FilterConversationDataSource {
     func reloadDataFor(accountId: String) {
         tempBag = DisposeBag()
         self.profileService.getAccountProfile(accountId: accountId)
-            .subscribe(onNext: { [unowned self] profile in
-                self.profileImageForCurrentAccount.onNext(profile)
+            .subscribe(onNext: { [weak self] profile in
+                self?.profileImageForCurrentAccount.onNext(profile)
             }).disposed(by: self.tempBag)
         self.conversationsService.conversationsForCurrentAccount
             .observeOn(MainScheduler.instance)
-            .subscribe(onNext: { [unowned self] conversations in
-                self.conversationsForCurrentAccount.onNext(conversations)
+            .subscribe(onNext: { [weak self] conversations in
+                self?.conversationsForCurrentAccount.onNext(conversations)
             }).disposed(by: self.tempBag)
     }
 
diff --git a/Ring/Ring/Features/Me/Me/AccountHeader.xib b/Ring/Ring/Features/Me/Me/AccountHeader.xib
index 14a4e42d8..faaf9628c 100644
--- a/Ring/Ring/Features/Me/Me/AccountHeader.xib
+++ b/Ring/Ring/Features/Me/Me/AccountHeader.xib
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
     <device id="retina4_7" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15510"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -17,7 +17,7 @@
                 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="CHp-YH-dZ5">
                     <rect key="frame" x="0.0" y="0.0" width="375" height="270"/>
                     <subviews>
-                        <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="bD3-jT-xLr">
+                        <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_contact_picture" translatesAutoresizingMaskIntoConstraints="NO" id="bD3-jT-xLr">
                             <rect key="frame" x="137.5" y="100" width="100" height="100"/>
                             <color key="tintColor" red="0.24705882352941178" green="0.42745098039215684" blue="0.65490196078431373" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
                             <constraints>
@@ -32,7 +32,7 @@
                             </userDefinedRuntimeAttributes>
                         </imageView>
                         <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="oQJ-jT-wTv">
-                            <rect key="frame" x="20" y="210" width="335" height="34"/>
+                            <rect key="frame" x="20" y="210" width="335" height="35"/>
                             <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
                             <textInputTraits key="textInputTraits"/>
                             <userDefinedRuntimeAttributes>
@@ -75,4 +75,7 @@
             <point key="canvasLocation" x="-97" y="130"/>
         </view>
     </objects>
+    <resources>
+        <image name="ic_contact_picture" width="128" height="128"/>
+    </resources>
 </document>
-- 
GitLab