NewScannedContactViewController.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2012-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import <Contacts/Contacts.h>
  21. #import <ContactsUI/ContactsUI.h>
  22. #import <MobileCoreServices/MobileCoreServices.h>
  23. #import "NewScannedContactViewController.h"
  24. #import "CryptoUtils.h"
  25. #import "Contact.h"
  26. #import "ContactStore.h"
  27. #import "StatusNavigationBar.h"
  28. #import "EditContactViewController.h"
  29. #import "EntityManager.h"
  30. #import "AvatarMaker.h"
  31. #import "AppDelegate.h"
  32. #import "UIDefines.h"
  33. #import "ModalPresenter.h"
  34. #import "BundleUtil.h"
  35. #import "GatewayAvatarMaker.h"
  36. #import "NSString+Hex.h"
  37. #import "FeatureMask.h"
  38. #import "ServerConnector.h"
  39. #import "BundleUtil.h"
  40. #import "Threema-Swift.h"
  41. #import "TrustedContacts.h"
  42. @interface NewScannedContactViewController ()
  43. @end
  44. @implementation NewScannedContactViewController {
  45. CNContactStore *cnAddressBook;
  46. Contact *dummyContact;
  47. EntityManager *tempEntityManager;
  48. }
  49. - (id)initWithCoder:(NSCoder *)aDecoder {
  50. self = [super initWithCoder:aDecoder];
  51. if (self) {
  52. cnAddressBook = [CNContactStore new];
  53. /* make a dummy contact (not Core Data managed) that we can pass to EditNameViewController */
  54. tempEntityManager = [[EntityManager alloc] init];
  55. dummyContact = tempEntityManager.entityCreator.contact;
  56. }
  57. return self;
  58. }
  59. - (void)viewDidLoad {
  60. [super viewDidLoad];
  61. self.contactImage.layer.masksToBounds = YES;
  62. self.contactImage.contentMode = UIViewContentModeScaleAspectFill;
  63. self.contactImage.layer.cornerRadius = self.contactImage.frame.size.width/2;
  64. if (dummyContact.isGatewayId) {
  65. self.editNameButton.hidden = YES;
  66. self.linkToContactCell.hidden = YES;
  67. self.threemaCallCell.hidden = YES;
  68. self.threemaTypeIcon.hidden = true;
  69. } else {
  70. UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tappedHeaderView)];
  71. [self.contactImage addGestureRecognizer:tapRecognizer];
  72. _threemaTypeIcon.image = [Utils threemaTypeIcon];
  73. if (!is64Bit) {
  74. self.threemaCallCell.hidden = YES;
  75. }
  76. }
  77. [self setupColors];
  78. }
  79. - (void)viewWillLayoutSubviews {
  80. [super viewWillLayoutSubviews];
  81. UIFontDescriptor *fontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleTitle3];
  82. CGFloat size = fontDescriptor.pointSize;
  83. _nameLabel.font = [UIFont boldSystemFontOfSize:size];
  84. }
  85. - (void)setupColors {
  86. [_nameLabel setTextColor:[Colors fontNormal]];
  87. _nameLabel.shadowColor = nil;
  88. UIImage *editNameImage = [_editNameButton.imageView.image imageWithTint:[Colors main]];
  89. [_editNameButton setImage:editNameImage forState:UIControlStateNormal];
  90. _editNameButton.accessibilityLabel = [BundleUtil localizedStringForKey:@"edit_contact"];
  91. if (@available(iOS 11.0, *)) {
  92. _contactImage.accessibilityIgnoresInvertColors = true;
  93. _threemaTypeIcon.accessibilityIgnoresInvertColors = true;
  94. }
  95. }
  96. - (void)viewWillAppear:(BOOL)animated {
  97. [super viewWillAppear:animated];
  98. [self updateView];
  99. }
  100. - (void)setIdentity:(NSString *)identity {
  101. _identity = identity;
  102. dummyContact.identity = identity;
  103. }
  104. - (BOOL)shouldAutorotate {
  105. return YES;
  106. }
  107. -(UIInterfaceOrientationMask)supportedInterfaceOrientations {
  108. if (SYSTEM_IS_IPAD) {
  109. return UIInterfaceOrientationMaskAll;
  110. }
  111. return UIInterfaceOrientationMaskAllButUpsideDown;
  112. }
  113. - (void)updateView {
  114. self.sendMessageLabel.text = [BundleUtil localizedStringForKey:@"send_message"];
  115. self.threemaCallLabel.text = [BundleUtil localizedStringForKey:@"call_voip_not_supported_title"];
  116. self.identityLabel.text = self.identity;
  117. self.keyFingerprintCell.fingerprintValueLabel.text = [CryptoUtils fingerprintForPublicKey:self.publicKey];
  118. // check if this is a trusted contact (like *THREEMA)
  119. if ([TrustedContacts isTrustedContactWithIdentity:self.identity publicKey:_publicKey]) {
  120. _verificationLevel = kVerificationLevelFullyVerified;
  121. }
  122. dummyContact.verificationLevel = [NSNumber numberWithInt:_verificationLevel];
  123. self.verificationLevelCell.contact = dummyContact;
  124. if ([LicenseStore requiresLicenseKey] == true) {
  125. self.threemaTypeIcon.hidden = [self.type isEqualToNumber:@1];
  126. } else {
  127. self.threemaTypeIcon.hidden = ![self.type isEqualToNumber:@1];
  128. }
  129. [self.contactImage setImage:[[AvatarMaker sharedAvatarMaker] avatarForContact:dummyContact size:self.contactImage.frame.size.width masked:NO]];
  130. if (self.cnContactId != nil) {
  131. if (cnAddressBook != nil) {
  132. [cnAddressBook requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
  133. if (granted == YES) {
  134. NSPredicate *predicate = [CNContact predicateForContactsWithIdentifiers:@[self.cnContactId]];
  135. NSError *error;
  136. NSArray *cnContacts = [cnAddressBook unifiedContactsMatchingPredicate:predicate keysToFetch:kCNContactKeys error:&error];
  137. if (error) {
  138. NSLog(@"error fetching contacts %@", error);
  139. } else {
  140. CNContact *person = cnContacts.firstObject;
  141. if (person != nil) {
  142. self.linkedContactNameLabel.text = [CNContactFormatter stringFromContact:person style:CNContactFormatterStyleFullName];
  143. dummyContact.firstName = person.givenName;
  144. dummyContact.lastName = person.familyName;
  145. }
  146. }
  147. }
  148. }];
  149. }
  150. self.editNameButton.enabled = NO;
  151. self.contactImage.userInteractionEnabled = NO;
  152. } else {
  153. self.linkedContactNameLabel.text = [BundleUtil localizedStringForKey:@"(none)"];
  154. self.editNameButton.enabled = YES;
  155. self.contactImage.userInteractionEnabled = YES;
  156. }
  157. self.nameLabel.text = dummyContact.displayName;
  158. if (dummyContact.isGatewayId) {
  159. [[GatewayAvatarMaker gatewayAvatarMaker] loadAvatarForId:dummyContact.identity onCompletion:^(UIImage *image) {
  160. [self.contactImage setImage:image];
  161. } onError:^(NSError *error) {
  162. }];
  163. }
  164. }
  165. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  166. if ([segue.identifier isEqualToString:@"EditName"]) {
  167. EditContactViewController *editVc = (EditContactViewController*)segue.destinationViewController;
  168. if ([dummyContact.firstName isEqual:self.identity])
  169. dummyContact.firstName = nil;
  170. editVc.contact = dummyContact;
  171. }
  172. }
  173. #pragma mark - Private functions
  174. - (Contact *)saveContact {
  175. NSString *dummyFirstName = dummyContact.firstName;
  176. NSString *dummyLastName = dummyContact.lastName;
  177. NSData *dummyImageData = dummyContact.imageData;
  178. // delete dummy contact first, otherwise it will be found when adding contact
  179. EntityManager *entityManager = [[EntityManager alloc] init];
  180. [entityManager performSyncBlockAndSafe:^{
  181. Contact *contact = (Contact *)[entityManager.entityFetcher getManagedObjectById:dummyContact.objectID];
  182. [[entityManager entityDestroyer] deleteObjectWithObject:contact];
  183. }];
  184. Contact *savedContact = [[ContactStore sharedContactStore] addContactWithIdentity:self.identity publicKey:self.publicKey cnContactId:self.cnContactId verificationLevel:_verificationLevel state:_state type:_type featureMask:self.featureMask alerts:YES];
  185. if (savedContact == nil) {
  186. return savedContact;
  187. }
  188. if (savedContact.isGatewayId) {
  189. [[GatewayAvatarMaker gatewayAvatarMaker] loadAndSaveAvatarForId:savedContact.identity];
  190. }
  191. if (self.cnContactId == nil) {
  192. [entityManager performSyncBlockAndSafe:^{
  193. Contact *contact = (Contact *)[entityManager.entityFetcher getManagedObjectById:savedContact.objectID];
  194. if (dummyFirstName && ![dummyFirstName isEqual:self.identity]) {
  195. contact.firstName = dummyFirstName;
  196. }
  197. if (dummyLastName) {
  198. contact.lastName = dummyLastName;
  199. }
  200. if (dummyImageData) {
  201. contact.imageData = dummyImageData;
  202. }
  203. }];
  204. }
  205. return savedContact;
  206. }
  207. #pragma mark - Table view delegate
  208. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  209. UITableViewCell *selectedCell = [self.tableView cellForRowAtIndexPath:indexPath];
  210. if (selectedCell == self.linkToContactCell) {
  211. CNContactPickerViewController *picker = [[CNContactPickerViewController alloc] init];
  212. picker.delegate = self;
  213. [self presentViewController:picker animated:YES completion:nil];
  214. }
  215. else if (selectedCell == self.sendMessageCell) {
  216. __block Contact *contact = [self saveContact];
  217. [self dismissViewControllerAnimated:YES completion:^{
  218. NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: contact, kKeyContact, [NSNumber numberWithBool:YES], kKeyForceCompose, nil];
  219. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowConversation object:nil userInfo:info];
  220. }];
  221. }
  222. else if (selectedCell == self.threemaCallCell) {
  223. __block Contact *contact = [self saveContact];
  224. [self dismissViewControllerAnimated:YES completion:^{
  225. NSInteger state = [[VoIPCallStateManager shared] currentCallState];
  226. if (state == CallStateIdle) {
  227. [FeatureMask checkFeatureMask:FEATURE_MASK_VOIP forContacts:[NSSet setWithObjects:contact, nil] onCompletion:^(NSArray *unsupportedContacts) {
  228. if (unsupportedContacts.count == 0) {
  229. [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
  230. if ([ServerConnector sharedServerConnector].connectionState == ConnectionStateLoggedIn) {
  231. VoIPCallUserAction *action = [[VoIPCallUserAction alloc] initWithAction:ActionCall contact:contact callId:nil completion:nil];
  232. [[VoIPCallStateManager shared] processUserAction:action];
  233. } else {
  234. // Alert no internet connection
  235. NSString *title = NSLocalizedString(@"cannot_connect_title", nil);
  236. NSString *message = NSLocalizedString(@"cannot_connect_message", nil);
  237. [UIAlertTemplate showAlertWithOwner:self title:title message:message actionOk:^(UIAlertAction * _Nonnull okAction) {
  238. [self.extensionContext completeRequestReturningItems:@[] completionHandler:^(BOOL expired) {
  239. }];
  240. }];
  241. }
  242. } else {
  243. [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
  244. [UIAlertTemplate showAlertWithOwner:self title:NSLocalizedString(@"call_voip_not_supported_title", nil) message:NSLocalizedString(@"call_voip_not_supported_text", nil) actionOk:nil];
  245. }
  246. }];
  247. } else {
  248. [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
  249. }
  250. }];
  251. }
  252. }
  253. - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
  254. if (indexPath.section == 1) {
  255. [Colors updateTableViewCellBackground:cell];
  256. [Colors setTextColor:[Colors main] inView:cell.contentView];
  257. } else {
  258. [Colors updateTableViewCell:cell];
  259. }
  260. }
  261. - (IBAction)save:(id)sender {
  262. [self saveContact];
  263. [self dismissViewControllerAnimated:YES completion:nil];
  264. }
  265. - (IBAction)cancel:(id)sender {
  266. [self dismissViewControllerAnimated:YES completion:nil];
  267. }
  268. - (IBAction)editContact:(id)sender {
  269. [self tappedHeaderView];
  270. }
  271. - (void)sendMessage {
  272. [self saveContact];
  273. [self dismissViewControllerAnimated:YES completion:^{
  274. // open chat of the contact
  275. }];
  276. }
  277. #pragma mark - People picker delegate
  278. -(void) contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact{
  279. self.cnContactId = contact.identifier;
  280. [self updateView];
  281. [self dismissViewControllerAnimated:YES completion:nil];
  282. }
  283. - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty {
  284. [self dismissViewControllerAnimated:YES completion:nil];
  285. }
  286. -(void)contactPickerDidCancel:(CNContactPickerViewController *)picker {
  287. self.cnContactId = nil;
  288. [self updateView];
  289. [self dismissViewControllerAnimated:YES completion:nil];
  290. }
  291. - (void)dealloc {
  292. [tempEntityManager rollback];
  293. }
  294. - (void)tappedHeaderView {
  295. if (self.cnContactId != nil) {
  296. [cnAddressBook requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
  297. if (granted == YES) {
  298. NSPredicate *predicate = [CNContact predicateForContactsWithIdentifiers:@[self.cnContactId]];
  299. NSError *error;
  300. NSArray *cnContacts = [cnAddressBook unifiedContactsMatchingPredicate:predicate keysToFetch:kCNContactKeys error:&error];
  301. if (error) {
  302. NSLog(@"error fetching contacts %@", error);
  303. } else {
  304. CNContact *person = cnContacts.firstObject;
  305. if (person != nil) {
  306. CNContactViewController *personVc = [CNContactViewController viewControllerForContact:person];
  307. personVc.allowsActions = NO;
  308. personVc.allowsEditing = YES;
  309. [self.navigationController pushViewController:personVc animated:YES];
  310. }
  311. }
  312. }
  313. }];
  314. } else {
  315. EditContactViewController *editVc = [self.storyboard instantiateViewControllerWithIdentifier:@"EditContactViewController"];
  316. editVc.contact = dummyContact;
  317. [self.navigationController pushViewController:editVc animated:YES];
  318. }
  319. }
  320. @end