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