Commit 6f9ee90f authored by Kateryna Kostiuk's avatar Kateryna Kostiuk Committed by Andreas Traczyk

contact request: add manager

This patch introduce manager to control conversations related to contact
request interactions

Change-Id: I4e841feb052d014d06bc3f272a1f038b84b086ce
Reviewed-by: Andreas Traczyk's avatarAndreas Traczyk <andreas.traczyk@savoirfairelinux.com>
parent f24f7157
......@@ -90,6 +90,7 @@
0E2D5F551F9145F200D574BF /* LinkNewDeviceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E2D5F541F9145F200D574BF /* LinkNewDeviceCell.xib */; };
0E403F811F7D797300C80BC2 /* MessageCellGenerated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E403F801F7D797300C80BC2 /* MessageCellGenerated.swift */; };
0E403F831F7D79B000C80BC2 /* MessageCellGenerated.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */; };
0E48F9D31FDF150700D6CC08 /* ContactRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */; };
0E6949791FA7E71C0029B60A /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6949781FA7E71C0029B60A /* BaseViewController.swift */; };
0E983E6E1FC77C3E0082103E /* ConversationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E983E6D1FC77C3E0082103E /* ConversationModel.swift */; };
0E9D84491FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9D84481FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift */; };
......@@ -335,6 +336,7 @@
0E2D5F541F9145F200D574BF /* LinkNewDeviceCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LinkNewDeviceCell.xib; sourceTree = "<group>"; };
0E403F801F7D797300C80BC2 /* MessageCellGenerated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellGenerated.swift; sourceTree = "<group>"; };
0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageCellGenerated.xib; sourceTree = "<group>"; };
0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestManager.swift; sourceTree = "<group>"; };
0E6949781FA7E71C0029B60A /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = "<group>"; };
0E983E6D1FC77C3E0082103E /* ConversationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationModel.swift; sourceTree = "<group>"; };
0E9D84481FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTabBarItemViewModel.swift; sourceTree = "<group>"; };
......@@ -677,6 +679,7 @@
62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */,
62DFAB2D1F9FF0D0002D6F9C /* NetworkService.swift */,
0EF78DE21FD0AE3000FC6966 /* ConversationsManager.swift */,
0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */,
);
path = Services;
sourceTree = "<group>";
......@@ -1553,6 +1556,7 @@
56AC650E1E85694D00EA1AA9 /* DesignableTextField.swift in Sources */,
1A2D189A1F2642C000B2C785 /* NotificationCenter+Ring.swift in Sources */,
1A2D18FC1F292DAD00B2C785 /* ConversationCell.swift in Sources */,
0E48F9D31FDF150700D6CC08 /* ContactRequestManager.swift in Sources */,
1A5DC0371F35675E0075E8EF /* ContactRequestCell.swift in Sources */,
1A20417C1F1E56FF00C08435 /* WelcomeViewModel.swift in Sources */,
1A5DC03D1F35678D0075E8EF /* ContactRequestItem.swift in Sources */,
......
......@@ -38,6 +38,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private let presenceService = PresenceService(withPresenceAdapter: PresenceAdapter())
private let networkService = NetworkService()
private var conversationManager: ConversationsManager?
private var contactRequestManager: ContactRequestManager?
public lazy var injectionBag: InjectionBag = {
return InjectionBag(withDaemonService: self.daemonService,
......@@ -78,6 +79,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
Chameleon.setGlobalThemeUsingPrimaryColor(UIColor.ringMain, withSecondaryColor: UIColor.ringSecondary, andContentStyle: .light)
Chameleon.setRingThemeUsingPrimaryColor(UIColor.ringMain, withSecondaryColor: UIColor.ringSecondary, andContentStyle: .light)
self.contactRequestManager = ContactRequestManager(accountService: self.accountService, contactService: self.contactsService, conversationService: self.conversationsService)
// load accounts during splashscreen
// and ask the AppCoordinator to handle the first screen once loading is finished
self.conversationManager = ConversationsManager(with: self.conversationsService, accountsService: self.accountService)
......
......@@ -304,4 +304,36 @@ final class InteractionDataHelper {
return false
}
}
func insertIfNotExist(item: Interaction) -> Bool {
guard let dataBase = RingDB.instance.ringDB else {
return false
}
let querySelect = table.filter(accountId == item.accountID &&
conversationId == item.conversationID &&
body == item.body &&
type == item.type)
let queryInsert = table.insert(accountId <- item.accountID,
authorId <- item.authorID,
conversationId <- item.conversationID,
timestamp <- item.timestamp,
body <- item.body,
type <- item.type,
status <- item.status,
daemonId <- item.daemonID)
do {
let rows = try dataBase.scalar(querySelect.count)
if rows == 0 {
let row = try dataBase.run(queryInsert)
guard row > 0 else {
return false
}
return true
}
} catch {
return false
}
return false
}
}
......@@ -112,7 +112,7 @@ class DBManager {
try interactionHepler.createTable()
}
func saveMessage(for accountUri: String, with contactUri: String, message: MessageModel, incoming: Bool) -> Completable {
func saveMessage(for accountUri: String, with contactUri: String, message: MessageModel, incoming: Bool, interactionType: InteractionType) -> Completable {
//create completable which will be executed on background thread
return Completable.create { [weak self] completable in
......@@ -150,8 +150,17 @@ class DBManager {
guard let conversationID = conversationsID.first else {
throw DBBridgingError.saveMessageFailed
}
// for now we have only one conversation between two persons(with group chat could be many)
if let success = self?.addMessageTo(conversation: conversationID, account: accountProfile.id, author: author, message: message), success {
var result: Bool?
switch interactionType {
case .text:
// for now we have only one conversation between two persons(with group chat could be many)
result = self?.addMessageTo(conversation: conversationID, account: accountProfile.id, author: author, message: message)
case .contact:
result = self?.addInteractionContactTo(conversation: conversationID, account: accountProfile.id, author: author, message: message)
default:
result = nil
}
if let success = result, success {
completable(.completed)
} else {
completable(.error(DBBridgingError.saveMessageFailed))
......@@ -357,27 +366,20 @@ class DBManager {
return participants.first
}
private func isGenerated(message: MessageModel) -> Bool {
switch message.content {
case GeneratedMessageType.contactRequestAccepted.rawValue:
return true
case GeneratedMessageType.receivedContactRequest.rawValue:
return true
case GeneratedMessageType.sendContactRequest.rawValue:
return true
default:
return false
}
}
private func convertToMessage(interaction: Interaction, author: String) -> MessageModel? {
if interaction.type != InteractionType.text.rawValue &&
interaction.type != InteractionType.contact.rawValue {
return nil
}
let date = Date(timeIntervalSince1970: TimeInterval(interaction.timestamp))
let message = MessageModel(withId: interaction.daemonID,
receivedDate: date,
content: interaction.body,
author: author,
incoming: interaction.incoming)
message.isGenerated = self.isGenerated(message: message)
if interaction.type == InteractionType.contact.rawValue {
message.isGenerated = true
}
if let status: InteractionStatus = InteractionStatus(rawValue: interaction.status) {
message.status = status.toMessageStatus()
}
......@@ -393,10 +395,24 @@ class DBManager {
let interaction = Interaction(defaultID, accountProfileID, authorProfileID,
conversationID, Int64(timeInterval),
message.content, InteractionType.text.rawValue,
InteractionStatus.unknown.rawValue, message.daemonId, message.incoming )
InteractionStatus.unknown.rawValue, message.daemonId,
message.incoming)
return self.interactionHepler.insert(item: interaction)
}
private func addInteractionContactTo(conversation conversationID: Int64,
account accountProfileID: Int64,
author authorProfileID: Int64,
message: MessageModel) -> Bool {
let timeInterval = message.receivedDate.timeIntervalSince1970
let interaction = Interaction(defaultID, accountProfileID, authorProfileID,
conversationID, Int64(timeInterval),
message.content, InteractionType.contact.rawValue,
InteractionStatus.read.rawValue, message.daemonId,
message.incoming)
return self.interactionHepler.insertIfNotExist(item: interaction)
}
private func getProfile(for profileUri: String, createIfNotExists: Bool) throws -> Profile? {
if let profile = try self.profileHepler.selectProfile(accountURI: profileUri) {
return profile
......
......@@ -49,17 +49,6 @@ class ContactRequestsViewModel: Stateable, ViewModel {
self.presenceService = injectionBag.presenceService
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]> = {
......@@ -87,25 +76,6 @@ 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!)
let message = MessageModel(withId: "",
receivedDate: Date(),
content: GeneratedMessageType.contactRequestAccepted.rawValue,
author: accountHelper.ringId!,
incoming: true)
message.isGenerated = true
self.conversationService.saveMessage(message: message,
toConversationWith: item.contactRequest.ringId,
toAccountId: (self.accountsService.currentAccount?.id)!,
toAccountUri: accountHelper.ringId!,
shouldRefreshConversations: 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)
......
......@@ -100,9 +100,7 @@ class ConversationViewModel: ViewModel {
.subscribe(onNext: { [unowned self] cont in
self.inviteButtonIsAvailable.onNext(!cont.confirmed)
if cont.confirmed {
self.generateMessage(ofType: GeneratedMessageType.contactRequestAccepted)
}
}).disposed(by: self.disposeBag)
// subscribe to presence updates for the conversation's associated contact
......@@ -264,15 +262,11 @@ class ConversationViewModel: ViewModel {
}
func sendContactRequest() {
let contactExists = self.contactsService.contact(withRingId: self.conversation.value.recipientRingId) != nil ? true : false
VCardUtils.loadVCard(named: VCardFiles.myProfile.rawValue,
inFolder: VCardFolders.profile.rawValue)
.subscribe(onSuccess: { [unowned self] (card) in
self.contactsService.sendContactRequest(toContactRingId: self.conversation.value.recipientRingId, vCard: card, withAccount: self.accountService.currentAccount!)
.subscribe(onCompleted: { [unowned self] in
if !contactExists {
self.generateMessage(ofType: GeneratedMessageType.sendContactRequest)
}
self.log.info("contact request sent")
}, onError: { [unowned self] (error) in
self.log.info(error)
......@@ -280,9 +274,6 @@ class ConversationViewModel: ViewModel {
}) { [unowned self] error in
self.contactsService.sendContactRequest(toContactRingId: self.conversation.value.recipientRingId, vCard: nil, withAccount: self.accountService.currentAccount!)
.subscribe(onCompleted: { [unowned self] in
if !contactExists {
self.generateMessage(ofType: GeneratedMessageType.sendContactRequest)
}
self.log.info("contact request sent")
}, onError: { [unowned self] (error) in
self.log.info(error)
......@@ -290,9 +281,4 @@ class ConversationViewModel: ViewModel {
}.disposed(by: self.disposeBag)
}
func generateMessage(ofType messageType: GeneratedMessageType) {
self.conversationsService.generateMessage(ofType: messageType,
forRindId: self.conversation.value.recipientRingId,
forAccount: self.accountService.currentAccount!)
}
}
/*
* Copyright (C) 2017 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
import RxSwift
class ContactRequestManager {
let accountService: AccountsService
let contactService: ContactsService
let conversationService: ConversationsService
let disposeBag = DisposeBag()
init(accountService: AccountsService, contactService: ContactsService, conversationService: ConversationsService) {
self.accountService = accountService
self.contactService = contactService
self.conversationService = conversationService
self.subscribeToContactEvents()
}
private func subscribeToContactEvents() {
self.contactService
.sharedResponseStream
.subscribe(onNext: { [unowned self] contactRequestEvent in
guard let accountID: String = contactRequestEvent.getEventInput(.accountId) else {
return
}
guard let contactRingId: String = contactRequestEvent.getEventInput(.uri) else {
return
}
guard let account = self.accountService.getAccount(fromAccountId: accountID) else {
return
}
guard let ringId = AccountModelHelper(withAccount: account).ringId else {
return
}
var shouldUpdateConversations = false
if let currentAccount = self.accountService.currentAccount {
if let currentrRingId = AccountModelHelper(withAccount: currentAccount).ringId, currentrRingId == ringId {
shouldUpdateConversations = true
}
}
var type: GeneratedMessageType
var date = Date()
if let receivedDate: Date = contactRequestEvent.getEventInput(.date) {
date = receivedDate
}
switch contactRequestEvent.eventType {
case ServiceEventType.contactAdded:
guard let contactConfirmed: Bool = contactRequestEvent.getEventInput(.state) else {
return
}
if !contactConfirmed {
return
}
type = GeneratedMessageType.contactRequestAccepted
case ServiceEventType.contactRequestSended:
type = GeneratedMessageType.sendContactRequest
case ServiceEventType.contactRequestReceived:
type = GeneratedMessageType.receivedContactRequest
default:
return
}
self.conversationService.generateMessage(ofType: type,
contactRingId: contactRingId,
accountRingId: ringId,
accountId: account.id,
date: date,
shouldUpdateConversation: shouldUpdateConversations)
})
.disposed(by: disposeBag)
}
}
......@@ -34,14 +34,20 @@ class ContactsService {
fileprivate let contactsAdapter: ContactsAdapter
fileprivate let log = SwiftyBeaver.self
fileprivate let disposeBag = DisposeBag()
let contactRequests = Variable([ContactRequestModel]())
let contacts = Variable([ContactModel]())
let contactStatus = PublishSubject<ContactModel>()
fileprivate let responseStream = PublishSubject<ServiceEvent>()
var sharedResponseStream: Observable<ServiceEvent>
init(withContactsAdapter contactsAdapter: ContactsAdapter) {
self.contactsAdapter = contactsAdapter
self.responseStream.disposed(by: disposeBag)
self.sharedResponseStream = responseStream.share()
ContactsAdapter.delegate = self
}
......@@ -98,6 +104,11 @@ class ContactsService {
let success = self.contactsAdapter.acceptTrustRequest(fromContact: contactRequest.ringId,
withAccountId: account.id)
if success {
var event = ServiceEvent(withEventType: .contactAdded)
event.addEventInput(.accountId, value: account.id)
event.addEventInput(.state, value: true)
event.addEventInput(.uri, value: contactRequest.ringId)
self.responseStream.onNext(event)
observable.on(.completed)
} else {
observable.on(.error(ContactServiceError.acceptTrustRequestFailed))
......@@ -133,6 +144,10 @@ class ContactsService {
payload = try CNContactVCardSerialization.dataWithImageAndUUID(from: vCard, andImageCompression: 40000)
}
self.contactsAdapter.sendTrustRequest(toContact: ringId, payload: payload, withAccountId: account.id)
var event = ServiceEvent(withEventType: .contactRequestSended)
event.addEventInput(.accountId, value: account.id)
event.addEventInput(.uri, value: ringId)
self.responseStream.onNext(event)
completable(.completed)
} catch {
completable(.error(ContactServiceError.vCardSerializationFailed))
......@@ -204,6 +219,11 @@ extension ContactsService: ContactsAdapterDelegate {
receivedDate: receivedDate,
accountId: accountId)
self.contactRequests.value.append(contactRequest)
var event = ServiceEvent(withEventType: .contactRequestReceived)
event.addEventInput(.accountId, value: accountId)
event.addEventInput(.uri, value: senderAccount)
event.addEventInput(.date, value: receivedDate)
self.responseStream.onNext(event)
} else {
// If the contact request already exists, update it's relevant data
if let contactRequest = self.contactRequest(withRingId: senderAccount) {
......@@ -223,6 +243,13 @@ extension ContactsService: ContactsAdapterDelegate {
if contact.confirmed != confirmed {
contact.confirmed = confirmed
self.contactStatus.onNext(contact)
if confirmed {
var event = ServiceEvent(withEventType: .contactAdded)
event.addEventInput(.state, value: confirmed)
event.addEventInput(.accountId, value: accountId)
event.addEventInput(.uri, value: uri)
self.responseStream.onNext(event)
}
}
}
//sync contacts with daemon contacts
......
......@@ -149,7 +149,8 @@ class ConversationsService {
self.dbManager.saveMessage(for: toAccountUri,
with: recipientRingId,
message: message,
incoming: message.incoming)
incoming: message.incoming,
interactionType: InteractionType.text)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.subscribe(onCompleted: { [weak self] in
if shouldRefreshConversations {
......@@ -178,44 +179,30 @@ class ConversationsService {
.first
}
// swiftlint:enable function_parameter_count
func generateMessage(ofType messageType: GeneratedMessageType,
forRindId ringId: String,
forAccount account: AccountModel) {
contactRingId: String,
accountRingId: String,
accountId: String,
date: Date,
shouldUpdateConversation: Bool) {
if let conversation = self.findConversation(withRingId: ringId, withAccountId: account.id) {
if self.generatedMessageExists(ofType: messageType, inConversation: conversation) {
return
}
}
let uri = AccountModelHelper(withAccount: account).ringId
let message = MessageModel(withId: "", receivedDate: date, content: messageType.rawValue, author: accountRingId, incoming: false)
message.isGenerated = true
let accountHelper = AccountModelHelper(withAccount: account)
let message = self.createMessage(withId: "",
withContent: messageType.rawValue,
byAuthor: accountHelper.ringId!,
generated: true,
incoming: true)
self.saveMessage(message: message,
toConversationWith: ringId,
toAccountId: account.id,
toAccountUri: uri!,
shouldRefreshConversations: true)
self.dbManager.saveMessage(for: accountRingId, with: contactRingId, message: message, incoming: false, interactionType: InteractionType.contact)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.subscribe(onCompleted: { [unowned self] in
self.log.debug("Message saved")
})
.disposed(by: disposeBag)
}
func generatedMessageExists(ofType messageType: GeneratedMessageType,
inConversation conversation: ConversationModel) -> Bool {
for message in conversation.messages
where message.content == messageType.rawValue {
return true
}
return false
if shouldUpdateConversation {
self.dbManager.getConversationsObservable(for: accountId, accountURI: accountRingId)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.subscribe(onNext: { conversationsModels in
self.conversations.value = conversationsModels
})
.disposed(by: (self.disposeBag))
}
}, onError: { _ in
}).disposed(by: self.disposeBag)
}
func status(forMessageId messageId: String) -> MessageStatus {
......
......@@ -32,6 +32,9 @@ enum ServiceEventType {
case messageStateChanged
case knownDevicesChanged
case exportOnRingEnded
case contactAdded
case contactRequestSended
case contactRequestReceived
}
/**
......@@ -46,6 +49,8 @@ enum ServiceEventInput {
case messageStatus
case messageId
case pin
case accountId
case date
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment