diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj index a73b9636d16f2aa0b11dbff3eafc7ae3376d6079..4b23f2aca470a071513faa9bdbf92e007db75828 100644 --- a/Ring/Ring.xcodeproj/project.pbxproj +++ b/Ring/Ring.xcodeproj/project.pbxproj @@ -157,8 +157,6 @@ 1A3CA32D1F13DA7200283748 /* Chameleon+Ring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A3CA32C1F13DA7200283748 /* Chameleon+Ring.swift */; }; 1A3D28A71F0EB9DB00B524EE /* Bool+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A3D28A61F0EB9DB00B524EE /* Bool+String.swift */; }; 1A3D28A91F0EBF0200B524EE /* UIView+Ring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A3D28A81F0EBF0200B524EE /* UIView+Ring.swift */; }; - 1A5DC01E1F355DA70075E8EF /* ContactsAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5DC01D1F355DA70075E8EF /* ContactsAdapterDelegate.swift */; }; - 1A5DC0201F355DCF0075E8EF /* ContactsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5DC01F1F355DCF0075E8EF /* ContactsService.swift */; }; 1A5DC0241F3564360075E8EF /* ContactRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5DC0231F3564360075E8EF /* ContactRequestModel.swift */; }; 1A5DC0281F3564AA0075E8EF /* MessageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5DC0271F3564AA0075E8EF /* MessageModel.swift */; }; 1A5DC02C1F3565250075E8EF /* MeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5DC02B1F3565250075E8EF /* MeViewController.swift */; }; @@ -200,6 +198,9 @@ 56BBC9DF1EDDC9D300CDAF8B /* LookupNameResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9DE1EDDC9D300CDAF8B /* LookupNameResponse.m */; }; 56C715FF1F0D36C600770048 /* ContactsAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 56C715FE1F0D36C600770048 /* ContactsAdapter.mm */; }; 5C093F011FB495830011D90E /* Differentiator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C093F001FB495830011D90E /* Differentiator.framework */; }; + 5C340DF51FCC6D5B00302D1A /* ContactRequestsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C340DF41FCC6D5B00302D1A /* ContactRequestsManager.swift */; }; + 5C9CF8391FCDA2D6005E3A69 /* ContactsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C716021F0D466100770048 /* ContactsService.swift */; }; + 5C9CF83A1FCDA2D6005E3A69 /* ContactsAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C716001F0D36D900770048 /* ContactsAdapterDelegate.swift */; }; 5CBADD081FC36DA5009CEFD7 /* ConversationModelHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBADD071FC36DA5009CEFD7 /* ConversationModelHelper.swift */; }; 5CE66F781FBF80B700EE9291 /* NewAccountsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE66F771FBF80B700EE9291 /* NewAccountsService.swift */; }; 5CE66F7C1FBF811200EE9291 /* InitialLoadingViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5CE66F7A1FBF811200EE9291 /* InitialLoadingViewController.storyboard */; }; @@ -401,8 +402,6 @@ 1A3D28A81F0EBF0200B524EE /* UIView+Ring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Ring.swift"; sourceTree = "<group>"; }; 1A5DC00A1F3558980075E8EF /* ContactsAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContactsAdapter.h; sourceTree = "<group>"; }; 1A5DC00D1F3559070075E8EF /* ContactsAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ContactsAdapter.mm; sourceTree = "<group>"; }; - 1A5DC01D1F355DA70075E8EF /* ContactsAdapterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsAdapterDelegate.swift; sourceTree = "<group>"; }; - 1A5DC01F1F355DCF0075E8EF /* ContactsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsService.swift; sourceTree = "<group>"; }; 1A5DC0231F3564360075E8EF /* ContactRequestModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactRequestModel.swift; sourceTree = "<group>"; }; 1A5DC0271F3564AA0075E8EF /* MessageModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageModel.swift; sourceTree = "<group>"; }; 1A5DC02B1F3565250075E8EF /* MeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeViewController.swift; sourceTree = "<group>"; }; @@ -453,6 +452,7 @@ 56C716001F0D36D900770048 /* ContactsAdapterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsAdapterDelegate.swift; sourceTree = "<group>"; }; 56C716021F0D466100770048 /* ContactsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsService.swift; sourceTree = "<group>"; }; 5C093F001FB495830011D90E /* Differentiator.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Differentiator.framework; path = Carthage/Build/iOS/Differentiator.framework; sourceTree = "<group>"; }; + 5C340DF41FCC6D5B00302D1A /* ContactRequestsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestsManager.swift; sourceTree = "<group>"; }; 5CBADD071FC36DA5009CEFD7 /* ConversationModelHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationModelHelper.swift; sourceTree = "<group>"; }; 5CE66F771FBF80B700EE9291 /* NewAccountsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewAccountsService.swift; sourceTree = "<group>"; }; 5CE66F7A1FBF811200EE9291 /* InitialLoadingViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = InitialLoadingViewController.storyboard; sourceTree = "<group>"; }; @@ -618,11 +618,10 @@ 564C44631E943E1E000F92B1 /* NameRegistrationAdapterDelegate.swift */, 56C716021F0D466100770048 /* ContactsService.swift */, 56C716001F0D36D900770048 /* ContactsAdapterDelegate.swift */, - 1A5DC01D1F355DA70075E8EF /* ContactsAdapterDelegate.swift */, - 1A5DC01F1F355DCF0075E8EF /* ContactsService.swift */, 62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */, 62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */, 62DFAB2D1F9FF0D0002D6F9C /* NetworkService.swift */, + 5C340DF41FCC6D5B00302D1A /* ContactRequestsManager.swift */, ); path = Services; sourceTree = "<group>"; @@ -1392,6 +1391,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5C9CF8391FCDA2D6005E3A69 /* ContactsService.swift in Sources */, + 5C9CF83A1FCDA2D6005E3A69 /* ContactsAdapterDelegate.swift in Sources */, 557086521E8ADB9D001A7CE4 /* SystemAdapter.mm in Sources */, 0E2D5F531F9145C800D574BF /* LinkNewDeviceCell.swift in Sources */, 0586C94B1F684DF600613517 /* UIImage+Helpers.swift in Sources */, @@ -1401,7 +1402,6 @@ 1A2D18F71F292D7200B2C785 /* MessageCellSent.swift in Sources */, 04399AAC1D1C304300E99CD9 /* AccountAdapter.mm in Sources */, 1AABA7461F0FE9C000739605 /* UIColor+Ring.swift in Sources */, - 1A5DC0201F355DCF0075E8EF /* ContactsService.swift in Sources */, 1A2D18C71F29180700B2C785 /* DeviceModel.swift in Sources */, 1A20418F1F1EAC0E00C08435 /* Coordinator.swift in Sources */, 1A2D18A11F27A6D600B2C785 /* LinkDeviceViewController.swift in Sources */, @@ -1414,7 +1414,6 @@ 1A5DC0241F3564360075E8EF /* ContactRequestModel.swift in Sources */, 1A5DC03F1F35678D0075E8EF /* ContactRequestsViewController.swift in Sources */, 1A20418B1F1EA58A00C08435 /* ViewModelBased.swift in Sources */, - 1A5DC01E1F355DA70075E8EF /* ContactsAdapterDelegate.swift in Sources */, 1A20418D1F1EABCC00C08435 /* StateableResponsive.swift in Sources */, 1A0C4EE51F1D67DF00550433 /* WalkthroughCoordinator.swift in Sources */, 1A2D18DD1F29192D00B2C785 /* MessableBubble.swift in Sources */, @@ -1458,6 +1457,7 @@ 04399AAE1D1C304300E99CD9 /* Utils.mm in Sources */, 56BBC9A31ED714DF00CDAF8B /* ConversationsService.swift in Sources */, 563AEC771EA664C0003A5641 /* RegistrationResponse.m in Sources */, + 5C340DF51FCC6D5B00302D1A /* ContactRequestsManager.swift in Sources */, 564C445B1E8EA44E000F92B1 /* Durations.swift in Sources */, 0EB479951FA28A7300106AFD /* ButtonTransparentBackground.swift in Sources */, 0EB1A5D11F8EBE23009923E2 /* DeviceCell.swift in Sources */, diff --git a/Ring/Ring/Account/VCardUtils.swift b/Ring/Ring/Account/VCardUtils.swift index a3506b48dd87bf464ac16c7550b059ba453aeb1f..572c4f5eb41a85aa408b02c4c1c9c54d7ec85242 100644 --- a/Ring/Ring/Account/VCardUtils.swift +++ b/Ring/Ring/Account/VCardUtils.swift @@ -30,23 +30,24 @@ enum VCardFolders: String { enum VCardFiles: String { case myProfile } + class VCardUtils { - class func saveVCard(vCard: CNContact, withName name: String, inFolder folder: String) -> Observable<Void> { - return Observable.create { observable in + class func saveVCard(vCard: CNContact, withName name: String, inFolder folder: String) -> Completable { + return Completable.create { completable in if let directoryURL = VCardUtils.getFilePath(forFile: name, inFolder: folder, createIfNotExists: true) { do { let data = try CNContactVCardSerialization.dataWithImageAndUUID(from: vCard, andImageCompression: nil) try data.write(to: directoryURL) - observable.on(.completed) + completable(.completed) } catch { - observable.on(.error(ContactServiceError.saveVCardFailed)) + completable(.error(ContactServiceError.saveVCardFailed)) } } else { - observable.on(.error(ContactServiceError.saveVCardFailed)) + completable(.error(ContactServiceError.saveVCardFailed)) } - return Disposables.create { } + return Disposables.create() } } diff --git a/Ring/Ring/AppDelegate.swift b/Ring/Ring/AppDelegate.swift index 5405ca805b590eaec3e54be848623d1edcd4ca55..80be19afcef2d028e69763bf06642b4c9692753a 100644 --- a/Ring/Ring/AppDelegate.swift +++ b/Ring/Ring/AppDelegate.swift @@ -48,13 +48,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate { withConversationService: self.conversationsService, withContactsService: self.contactsService, withPresenceService: self.presenceService, - withNetworkService: self.networkService + withNetworkService: self.networkService, + withContactRequestsManager: self.contactRequestsManager ) }() + private lazy var appCoordinator: AppCoordinator = { return AppCoordinator(with: self.injectionBag) }() + private lazy var contactRequestsManager: ContactRequestsManager = { + return ContactRequestsManager(with: self.newAccountsService, + contactsService: self.contactsService, + conversationsService: self.conversationsService, + presenceService: self.presenceService) + }() + private let log = SwiftyBeaver.self private let disposeBag = DisposeBag() diff --git a/Ring/Ring/Coordinators/InjectionBag.swift b/Ring/Ring/Coordinators/InjectionBag.swift index 87e8ac1f0f5f1ebc15cb09bb274f2f05e5d5c648..a8390c82017743b2a1802529abd68e35accbe4ed 100644 --- a/Ring/Ring/Coordinators/InjectionBag.swift +++ b/Ring/Ring/Coordinators/InjectionBag.swift @@ -30,6 +30,8 @@ class InjectionBag { let presenceService: PresenceService let networkService: NetworkService + let contactRequestsManager: ContactRequestsManager + init (withDaemonService daemonService: DaemonService, withAccountService accountService: AccountsService, withNewAccountsService newAccountsService: NewAccountsService, @@ -37,7 +39,8 @@ class InjectionBag { withConversationService conversationService: ConversationsService, withContactsService contactsService: ContactsService, withPresenceService presenceService: PresenceService, - withNetworkService networkService: NetworkService) { + withNetworkService networkService: NetworkService, + withContactRequestsManager contactRequestsManager: ContactRequestsManager) { self.daemonService = daemonService self.accountService = accountService self.newAccountsService = newAccountsService @@ -46,6 +49,7 @@ class InjectionBag { self.contactsService = contactsService self.presenceService = presenceService self.networkService = networkService + self.contactRequestsManager = contactRequestsManager } } diff --git a/Ring/Ring/EditProfileViewModel.swift b/Ring/Ring/EditProfileViewModel.swift index c3ebad5a1ce3a737703e995a85bf2a3989d85d75..2be8450eed1d81753d16956de4fdd48b2f0377ea 100644 --- a/Ring/Ring/EditProfileViewModel.swift +++ b/Ring/Ring/EditProfileViewModel.swift @@ -42,14 +42,16 @@ class EditProfileViewModel { } func saveProfile() { - let vcard = CNMutableContact() if let image = self.image.value, !image.isEqual(defaultImage) { vcard.imageData = UIImagePNGRepresentation(image) } - vcard.familyName = self.profileName.value - _ = VCardUtils.saveVCard(vCard: vcard, withName: VCardFiles.myProfile.rawValue, inFolder: VCardFolders.profile.rawValue).subscribe() - + vcard.familyName = self.profileName.value + VCardUtils.saveVCard(vCard: vcard, + withName: VCardFiles.myProfile.rawValue, + inFolder: VCardFolders.profile.rawValue) + .subscribe() + .disposed(by: self.disposeBag) } func updateImage(_ image: Image) { diff --git a/Ring/Ring/Features/ContactRequests/ContactRequestsViewController.swift b/Ring/Ring/Features/ContactRequests/ContactRequestsViewController.swift index 705db7f2467d15238b91632fe26aeb25c79c85cd..897fb870c5a26a41da5e6c782d4982f43d60621a 100644 --- a/Ring/Ring/Features/ContactRequests/ContactRequestsViewController.swift +++ b/Ring/Ring/Features/ContactRequests/ContactRequestsViewController.swift @@ -146,7 +146,6 @@ class ContactRequestsViewController: UIViewController, StoryboardBased, ViewMode } func setupBindings() { - self.viewModel .hasInvitations .observeOn(MainScheduler.instance) @@ -155,26 +154,26 @@ class ContactRequestsViewController: UIViewController, StoryboardBased, ViewMode } func acceptButtonTapped(withItem item: ContactRequestItem) { - viewModel.accept(withItem: item).subscribe(onError: { [unowned self] error in - self.log.error("Accept trust request failed") - }, onCompleted: { [unowned self] in - self.log.info("Accept trust request done") + self.viewModel.accept(withItem: item).subscribe(onCompleted: { [weak self] in + self?.log.info("Accept trust request done") + }, onError: { [weak self] error in + self?.log.error("Accept trust request failed") }).disposed(by: self.disposeBag) } func discardButtonTapped(withItem item: ContactRequestItem) { - viewModel.discard(withItem: item).subscribe(onError: { [unowned self] error in - self.log.error("Discard trust request failed") - }, onCompleted: { [unowned self] in - self.log.info("Discard trust request done") + self.viewModel.discard(withItem: item).subscribe(onCompleted: { [weak self] in + self?.log.info("Discard trust request done") + }, onError: { [weak self] error in + self?.log.error("Discard trust request failed") }).disposed(by: self.disposeBag) } func banButtonTapped(withItem item: ContactRequestItem) { - viewModel.ban(withItem: item).subscribe(onError: { [unowned self] error in - self.log.error("Ban trust request failed") - }, onCompleted: { [unowned self] in - self.log.info("Ban trust request done") + self.viewModel.ban(withItem: item).subscribe(onCompleted: { [weak self] in + self?.log.info("Ban trust request done") + }, onError: { [weak self] (error) in + self?.log.error("Ban trust request failed") }).disposed(by: self.disposeBag) } } diff --git a/Ring/Ring/Features/ContactRequests/ContactRequestsViewModel.swift b/Ring/Ring/Features/ContactRequests/ContactRequestsViewModel.swift index b71b934bcb1075e1dd6b221f5df789b3bd03d813..ce22baa3a4c9d2279f0e949977667b612e531184 100644 --- a/Ring/Ring/Features/ContactRequests/ContactRequestsViewModel.swift +++ b/Ring/Ring/Features/ContactRequests/ContactRequestsViewModel.swift @@ -2,6 +2,7 @@ * Copyright (C) 2017 Savoir-faire Linux Inc. * * Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com> + * Author: Romain Bertozzi <romain.bertozzi@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 @@ -22,7 +23,7 @@ import RxSwift import Contacts import SwiftyBeaver -class ContactRequestsViewModel: Stateable, ViewModel { +final class ContactRequestsViewModel: Stateable, ViewModel { // MARK: - Rx Stateable private let stateSubject = PublishSubject<State>() @@ -30,51 +31,43 @@ class ContactRequestsViewModel: Stateable, ViewModel { return self.stateSubject.asObservable() }() - let contactsService: ContactsService - let accountsService: AccountsService - let conversationService: ConversationsService - let nameService: NameService - let presenceService: PresenceService + private let contactsService: ContactsService + private let accountsService: NewAccountsService + private let conversationService: ConversationsService + fileprivate let nameService: NameService + private let presenceService: PresenceService + private let contactRequestsManager: ContactRequestsManager fileprivate let disposeBag = DisposeBag() - fileprivate let log = SwiftyBeaver.self + private let log = SwiftyBeaver.self - fileprivate let injectionBag: InjectionBag + private let injectionBag: InjectionBag required init(with injectionBag: InjectionBag) { self.contactsService = injectionBag.contactsService - self.accountsService = injectionBag.accountService + self.accountsService = injectionBag.newAccountsService self.conversationService = injectionBag.conversationsService self.nameService = injectionBag.nameService self.presenceService = injectionBag.presenceService + self.contactRequestsManager = injectionBag.contactRequestsManager self.injectionBag = injectionBag - - self.contactsService.contactRequests - .asObservable() - .subscribe(onNext: {[unowned self] contactRequests in - guard let account = self.accountsService.currentAccount else { return } - guard let ringId = contactRequests.last?.ringId else { return } - self.conversationService.generateMessage(ofType: GeneratedMessageType.receivedContactRequest, - forRindId: ringId, - forAccount: account) - }) - .disposed(by: self.disposeBag) } lazy var contactRequestItems: Observable<[ContactRequestItem]> = { - return self.contactsService.contactRequests - .asObservable() - .map({ [unowned self] contactRequests in - return contactRequests - .filter { $0.accountId == self.accountsService.currentAccount?.id } - .sorted { $0.receivedDate > $1.receivedDate } - .map { contactRequest in - let item = ContactRequestItem(withContactRequest: contactRequest) - self.lookupUserName(withItem: item) - return item - } - }) + let contactRequestsObs = self.contactsService.contactRequests.asObservable() + let currentAccountObs = self.accountsService.currentAccount().asObservable() + + return Observable.combineLatest(contactRequestsObs, currentAccountObs, resultSelector: { (requests, account) -> [ContactRequestItem] in + return requests + .filter { $0.accountId == account.id } + .sorted { $0.receivedDate > $1.receivedDate } + .map { contactRequest in + let item = ContactRequestItem(withContactRequest: contactRequest) + //TODO: Lookup username + return item + } + }) }() lazy var hasInvitations: Observable<Bool> = { @@ -85,77 +78,62 @@ class ContactRequestsViewModel: Stateable, ViewModel { }) }() - func accept(withItem item: ContactRequestItem) -> Observable<Void> { - let acceptCompleted = self.contactsService.accept(contactRequest: item.contactRequest, withAccount: self.accountsService.currentAccount!) - - let accountHelper = AccountModelHelper(withAccount: self.accountsService.currentAccount!) - self.conversationService.saveMessage(withId: "", - withContent: GeneratedMessageType.contactRequestAccepted.rawValue, - byAuthor: accountHelper.ringId!, - toConversationWith: item.contactRequest.ringId, - currentAccountId: (self.accountsService.currentAccount?.id)!, generated: true) - .subscribe(onCompleted: { [unowned self] in - self.log.debug("Message saved") - }) - .disposed(by: disposeBag) - - self.presenceService.subscribeBuddy(withAccountId: (self.accountsService.currentAccount?.id)!, - withUri: item.contactRequest.ringId, - withFlag: true) - - if let vCard = item.contactRequest.vCard { - let saveVCardCompleted = self.contactsService.saveVCard(vCard: vCard, forContactWithRingId: item.contactRequest.ringId) - return Observable<Void>.zip(acceptCompleted, saveVCardCompleted) { _, _ in - return - } - } else { - return acceptCompleted.asObservable() - } - } - - func discard(withItem item: ContactRequestItem) -> Observable<Void> { - return self.contactsService.discard(contactRequest: item.contactRequest, - withAccount: self.accountsService.currentAccount!) + func accept(withItem item: ContactRequestItem) -> Completable { + return self.accountsService.currentAccount().asObservable() + .flatMap { [unowned self] (account) -> Completable in + return self.contactRequestsManager.accept(contactRequest: item.contactRequest, + account: account) + }.asCompletable() } - func ban(withItem item: ContactRequestItem) -> Observable<Void> { - let discardCompleted = self.contactsService.discard(contactRequest: item.contactRequest, - withAccount: self.accountsService.currentAccount!) - - let removeCompleted = self.contactsService.removeContact(withRingId: item.contactRequest.ringId, - ban: true, - withAccount: self.accountsService.currentAccount!) - - return Observable<Void>.zip(discardCompleted, removeCompleted) { _, _ in - return - } + func discard(withItem item: ContactRequestItem) -> Completable { + return self.accountsService.currentAccount().asObservable() + .flatMap { [unowned self] (account) -> Completable in + return self.contactRequestsManager.discard(contactRequest: item.contactRequest, + account: account) + }.asCompletable() } - fileprivate func lookupUserName(withItem item: ContactRequestItem) { - - self.nameService.usernameLookupStatus.asObservable() - .filter({ lookupNameResponse in - return lookupNameResponse.address == item.contactRequest.ringId - }) - .subscribe(onNext: { lookupNameResponse in - if lookupNameResponse.state == .found && !lookupNameResponse.name.isEmpty { - item.userName.value = lookupNameResponse.name - } else { - item.userName.value = lookupNameResponse.address - } - }) - .disposed(by: self.disposeBag) - - self.nameService.lookupAddress(withAccount: (accountsService.currentAccount?.id)!, - nameserver: "", - address: item.contactRequest.ringId) + func ban(withItem item: ContactRequestItem) -> Completable { + return self.accountsService.currentAccount().asObservable() + .flatMap { [unowned self] (account) -> Completable in + return self.contactRequestsManager.ban(contactRequest: item.contactRequest, + account: account) + }.asCompletable() } func showConversation (forRingId ringId: String) { - let conversationViewModel = ConversationViewModel(with: self.injectionBag) - let conversation = self.conversationService.findConversation(withRingId: ringId, - withAccountId: (accountsService.currentAccount?.id)!) - conversationViewModel.conversation = conversation - self.stateSubject.onNext(ConversationsState.conversationDetail(conversationViewModel: conversationViewModel)) + self.accountsService.currentAccount().subscribe(onSuccess: { [unowned self] (account) in + let conversationViewModel = ConversationViewModel(with: self.injectionBag) + let conversation = self.conversationService.findConversation(withRingId: ringId, + withAccountId: account.id) + conversationViewModel.conversation = conversation + self.stateSubject.onNext(ConversationsState.conversationDetail(conversationViewModel: conversationViewModel)) + }, onError: { [unowned self] (error) in + self.log.error("No account available") + }).disposed(by: self.disposeBag) } } + +//extension ContactRequestsViewModel { +// +// fileprivate func lookupUserName(withItem item: ContactRequestItem) { +// self.nameService.usernameLookupStatus.asObservable() +// .filter({ lookupNameResponse in +// return lookupNameResponse.address == item.contactRequest.ringId +// }) +// .subscribe(onNext: { lookupNameResponse in +// if lookupNameResponse.state == .found && !lookupNameResponse.name.isEmpty { +// item.userName.value = lookupNameResponse.name +// } else { +// item.userName.value = lookupNameResponse.address +// } +// }) +// .disposed(by: self.disposeBag) +// +// self.nameService.lookupAddress(withAccount: (accountsService.currentAccount?.id)!, +// nameserver: "", +// address: item.contactRequest.ringId) +// } +// +//} diff --git a/Ring/Ring/Services/ContactRequestsManager.swift b/Ring/Ring/Services/ContactRequestsManager.swift new file mode 100644 index 0000000000000000000000000000000000000000..07a3fc2411b49ed98f90e8867d76a75d80a3fa2f --- /dev/null +++ b/Ring/Ring/Services/ContactRequestsManager.swift @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Romain Bertozzi <romain.bertozzi@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 RxSwift + +final class ContactRequestsManager { + + let accountsService: NewAccountsService + let contactsService: ContactsService + let conversationsService: ConversationsService + let presenceService: PresenceService + + let disposeBag = DisposeBag() + + init(with accountsService: NewAccountsService, + contactsService: ContactsService, + conversationsService: ConversationsService, + presenceService: PresenceService) { + self.accountsService = accountsService + self.contactsService = contactsService + self.conversationsService = conversationsService + self.presenceService = presenceService + + self.handleNewContactsRequests() + } + + private func handleNewContactsRequests() { + let currentAccountObservable = self.accountsService.currentAccount().asObservable() + let contactRequestsObservable = self.contactsService.contactRequests.asObservable() + + Observable + .combineLatest(currentAccountObservable, contactRequestsObservable) { [weak self] (account, requests) in + guard let ringId = requests.last?.ringId else { return } + self?.conversationsService.generateMessage(ofType: GeneratedMessageType.receivedContactRequest, + forRindId: ringId, + forAccount: account) + } + .subscribe() + .disposed(by: self.disposeBag) + } + + func accept(contactRequest: ContactRequestModel, account: AccountModel) -> Completable { + let accountHelper = AccountModelHelper(withAccount: account) + let completable = self.contactsService.accept(contactRequest: contactRequest, withAccount: account) + .andThen(self.conversationsService.saveMessage(withId: "", + withContent: GeneratedMessageType.contactRequestAccepted.rawValue, + byAuthor: accountHelper.ringId!, + toConversationWith: contactRequest.ringId, + currentAccountId: account.id, + generated: true)) + .andThen(self.presenceService.subscribeBuddy(withAccountId: account.id, + withUri: contactRequest.ringId, + withFlag: true)) + if let vcard = contactRequest.vCard { + completable.andThen(self.contactsService.saveVCard(vCard: vcard, + forContactWithRingId: contactRequest.ringId)) + } + return completable + } + + func discard(contactRequest: ContactRequestModel, account: AccountModel) -> Completable { + return self.contactsService.discard(contactRequest: contactRequest, + withAccount: account) + } + + func ban(contactRequest: ContactRequestModel, account: AccountModel) -> Completable { + let discard = self.contactsService.discard(contactRequest: contactRequest, + withAccount: account) + let remove = self.contactsService.removeContact(withRingId: contactRequest.ringId, + ban: true, + withAccount: account) + return discard.andThen(remove) + } + +} diff --git a/Ring/Ring/Services/ContactsService.swift b/Ring/Ring/Services/ContactsService.swift index 8fae60a097f39377ef4ddecd228436b020d37fb8..07f41ba5d5ccd414a054ae73032ba8dd958c79f1 100644 --- a/Ring/Ring/Services/ContactsService.swift +++ b/Ring/Ring/Services/ContactsService.swift @@ -93,22 +93,22 @@ class ContactsService { } } - func accept(contactRequest: ContactRequestModel, withAccount account: AccountModel) -> Observable<Void> { - return Observable.create { [unowned self] observable in + func accept(contactRequest: ContactRequestModel, withAccount account: AccountModel) -> Completable { + return Completable.create { [unowned self] completable in let success = self.contactsAdapter.acceptTrustRequest(fromContact: contactRequest.ringId, withAccountId: account.id) if success { - observable.on(.completed) + completable(.completed) } else { - observable.on(.error(ContactServiceError.acceptTrustRequestFailed)) + completable(.error(ContactServiceError.acceptTrustRequestFailed)) } - return Disposables.create { } + return Disposables.create () } } - func discard(contactRequest: ContactRequestModel, withAccount account: AccountModel) -> Observable<Void> { - return Observable.create { [unowned self] observable in + func discard(contactRequest: ContactRequestModel, withAccount account: AccountModel) -> Completable { + return Completable.create { [unowned self] completable in let success = self.contactsAdapter.discardTrustRequest(fromContact: contactRequest.ringId, withAccountId: account.id) @@ -116,11 +116,11 @@ class ContactsService { self.removeContactRequest(withRingId: contactRequest.ringId) if success { - observable.on(.completed) + completable(.completed) } else { - observable.on(.error(ContactServiceError.diacardTrusRequestFailed)) + completable(.error(ContactServiceError.diacardTrusRequestFailed)) } - return Disposables.create { } + return Disposables.create() } } @@ -150,16 +150,17 @@ class ContactsService { } } - func removeContact(contact: ContactModel, ban: Bool, withAccount account: AccountModel) -> Observable<Void> { + func removeContact(contact: ContactModel, ban: Bool, withAccount account: AccountModel) -> Completable { return removeContact(withRingId: contact.ringId, ban: ban, withAccount: account) } - func removeContact(withRingId ringId: String, ban: Bool, withAccount account: AccountModel) -> Observable<Void> { - return Observable.create { [unowned self] observable in + func removeContact(withRingId ringId: String, ban: Bool, withAccount account: AccountModel) -> Completable { + return Completable.create { [unowned self] completable in self.contactsAdapter.removeContact(withURI: ringId, accountId: account.id, ban: ban) self.removeContactRequest(withRingId: ringId) - observable.on(.completed) - return Disposables.create { } + completable(.completed) + + return Disposables.create() } } @@ -249,9 +250,10 @@ extension ContactsService: ContactsAdapterDelegate { // MARK: - profile - func saveVCard(vCard: CNContact, forContactWithRingId ringID: String) -> Observable<Void> { - let vCardSaved = VCardUtils.saveVCard(vCard: vCard, withName: ringID, inFolder: VCardFolders.contacts.rawValue) - return vCardSaved + func saveVCard(vCard: CNContact, forContactWithRingId ringID: String) -> Completable { + return VCardUtils.saveVCard(vCard: vCard, + withName: ringID, + inFolder: VCardFolders.contacts.rawValue) } func loadVCard(forContactWithRingId ringID: String) -> Single<CNContact> { diff --git a/Ring/Ring/Services/PresenceService.swift b/Ring/Ring/Services/PresenceService.swift index c318df8967307f5ae9643d22feb4851fae2c5f75..c19ee8a2dcff8ed9b3733e542d6d7d5084c79475 100644 --- a/Ring/Ring/Services/PresenceService.swift +++ b/Ring/Ring/Services/PresenceService.swift @@ -39,19 +39,26 @@ class PresenceService { PresenceAdapter.delegate = self } - func subscribeBuddies(withAccount account: AccountModel, withContacts contacts: [ContactModel]) { + func subscribeBuddies(withAccount account: AccountModel, withContacts contacts: [ContactModel]) -> Completable { + var subscriptions = [Completable]() for contact in contacts { - subscribeBuddy(withAccountId: account.id, - withUri: contact.ringId, - withFlag: true) + let subscription = self.subscribeBuddy(withAccountId: account.id, + withUri: contact.ringId, + withFlag: true) + subscriptions.append(subscription) } + return Completable.merge(subscriptions) } func subscribeBuddy(withAccountId accountId: String, withUri uri: String, - withFlag flag: Bool) { - presenceAdapter.subscribeBuddy(withURI: uri, withAccountId: accountId, withFlag: flag) - contactPresence[uri] = false + withFlag flag: Bool) -> Completable { + return Completable.create(subscribe: { [unowned self] (completable) -> Disposable in + self.presenceAdapter.subscribeBuddy(withURI: uri, withAccountId: accountId, withFlag: flag) + self.contactPresence[uri] = false + completable(.completed) + return Disposables.create() + }) } }