Commit 3b382a32 authored by Kateryna Kostiuk's avatar Kateryna Kostiuk

profile: save vCard

This patch creates and saves vCard for user profile.

Change-Id: I5487ff8cafa116975c3081c906cfb426ded5fc31
parent 330f20d1
......@@ -83,6 +83,8 @@
04399B141D1C341A00E99CD9 /* libx264.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 04399AE21D1C341A00E99CD9 /* libx264.a */; };
04399B151D1C341A00E99CD9 /* libyaml-cpp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 04399AE31D1C341A00E99CD9 /* libyaml-cpp.a */; };
0586C94B1F684DF600613517 /* UIImage+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0586C94A1F684DF600613517 /* UIImage+Helpers.swift */; };
0EE1B54E1F75ACDE00BA98EE /* CNContactVCardSerialization+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE1B54D1F75ACDE00BA98EE /* CNContactVCardSerialization+Helpers.swift */; };
0EE1B5501F75AD4700BA98EE /* VCardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE1B54F1F75AD4700BA98EE /* VCardUtils.swift */; };
1A0C4EDA1F1D4B1B00550433 /* WelcomeViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1A0C4ED91F1D4B1B00550433 /* WelcomeViewController.storyboard */; };
1A0C4EDC1F1D4B7E00550433 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0C4EDB1F1D4B7E00550433 /* WelcomeViewController.swift */; };
1A0C4EE31F1D673600550433 /* InjectionBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0C4EE21F1D673600550433 /* InjectionBag.swift */; };
......@@ -299,6 +301,8 @@
04399AE21D1C341A00E99CD9 /* libx264.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libx264.a; path = ../fat/lib/libx264.a; sourceTree = "<group>"; };
04399AE31D1C341A00E99CD9 /* libyaml-cpp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libyaml-cpp.a"; path = "../fat/lib/libyaml-cpp.a"; sourceTree = "<group>"; };
0586C94A1F684DF600613517 /* UIImage+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Helpers.swift"; sourceTree = "<group>"; };
0EE1B54D1F75ACDE00BA98EE /* CNContactVCardSerialization+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CNContactVCardSerialization+Helpers.swift"; sourceTree = "<group>"; };
0EE1B54F1F75AD4700BA98EE /* VCardUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VCardUtils.swift; sourceTree = "<group>"; };
1A0C4ED91F1D4B1B00550433 /* WelcomeViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = WelcomeViewController.storyboard; path = Welcome/WelcomeViewController.storyboard; sourceTree = "<group>"; };
1A0C4EDB1F1D4B7E00550433 /* WelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WelcomeViewController.swift; path = Welcome/WelcomeViewController.swift; sourceTree = "<group>"; };
1A0C4EE21F1D673600550433 /* InjectionBag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InjectionBag.swift; sourceTree = "<group>"; };
......@@ -572,6 +576,7 @@
isa = PBXGroup;
children = (
5516C29E1E71CEFF009D3D2D /* AccountModelHelper.swift */,
0EE1B54F1F75AD4700BA98EE /* VCardUtils.swift */,
);
path = Account;
sourceTree = "<group>";
......@@ -624,6 +629,7 @@
1A2D189B1F264AD900B2C785 /* UIViewController+Ring.swift */,
1A2D18A51F27F7A400B2C785 /* UIViewController+Rx.swift */,
0586C94A1F684DF600613517 /* UIImage+Helpers.swift */,
0EE1B54D1F75ACDE00BA98EE /* CNContactVCardSerialization+Helpers.swift */,
);
path = Extensions;
sourceTree = "<group>";
......@@ -1286,6 +1292,7 @@
62A88D3B1F6C3ACC00F8AB18 /* PresenceService.swift in Sources */,
1A2D18E51F29197100B2C785 /* MessageAccessoryView.swift in Sources */,
1A2D18C61F29180700B2C785 /* ConversationModel.swift in Sources */,
0EE1B54E1F75ACDE00BA98EE /* CNContactVCardSerialization+Helpers.swift in Sources */,
56308BA71EA00E5700660275 /* NameRegistrationResponse.m in Sources */,
1A3CA32D1F13DA7200283748 /* Chameleon+Ring.swift in Sources */,
1ABE07E21F0D924700D36361 /* Strings.swift in Sources */,
......@@ -1328,6 +1335,7 @@
1A2041801F1E903B00C08435 /* CreateProfileViewController.swift in Sources */,
04399AAD1D1C304300E99CD9 /* DRingAdapter.mm in Sources */,
1A2D18D11F29182500B2C785 /* ConversationViewController.swift in Sources */,
0EE1B5501F75AD4700BA98EE /* VCardUtils.swift in Sources */,
0273C2FF1E0C438F00CF00BA /* AccountAdapterDelegate.swift in Sources */,
1A2D19011F29353A00B2C785 /* MeDetailViewModel.swift in Sources */,
1A2D18A41F27EF5200B2C785 /* AppCoordinator.swift in Sources */,
......
/*
* 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 Contacts
import RxSwift
enum VCardFolders: String {
case contacts
case profile
}
class VCardUtils {
class func saveVCard(vCard: CNContact, withName name: String, inFolder folder: String) -> Observable<Void> {
return Observable.create { observable 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)
} catch {
observable.on(.error(ContactServiceError.saveVCardFailed))
}
} else {
observable.on(.error(ContactServiceError.saveVCardFailed))
}
return Disposables.create { }
}
}
class func loadVCard(named name: String, inFolder folder: String) -> Single<CNContact> {
return Single.create(subscribe: { single in
if let directoryURL = VCardUtils.getFilePath(forFile: name, inFolder: folder, createIfNotExists: false) {
do {
if let data = FileManager.default.contents(atPath: directoryURL.path) {
let vCard = try CNContactVCardSerialization.contacts(with: data)
if vCard.isEmpty {
single(.error(ContactServiceError.loadVCardFailed))
} else {
single(.success(vCard.first!))
}
}
} catch {
single(.error(ContactServiceError.loadVCardFailed))
}
} else {
single(.error(ContactServiceError.loadVCardFailed))
}
return Disposables.create { }
})
}
class func getFilePath(forFile fileName: String, inFolder folderName: String, createIfNotExists shouldCreate: Bool) -> URL? {
var path: URL?
guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return path
}
let directoryURL = documentsURL.appendingPathComponent(folderName)
var isDirectory = ObjCBool(true)
let directoryExists = FileManager.default.fileExists(atPath: directoryURL.path, isDirectory: &isDirectory)
if directoryExists && isDirectory.boolValue {
path = directoryURL.appendingPathComponent(fileName)
return path
}
if !shouldCreate {
return path
}
do {
try FileManager.default.createDirectory(atPath: directoryURL.path, withIntermediateDirectories: true, attributes: nil)
path = directoryURL.appendingPathComponent(fileName)
return path
} catch _ as NSError {
return path
}
}
}
......@@ -33,5 +33,5 @@
#import "Chameleon/Chameleon.h"
#import "ContactsAdapter.h"
#import "PresenceAdapter.h"
#import <CommonCrypto/CommonCrypto.h>
#import <Contacts/Contacts.h>
/*
* 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 Contacts
/*
*This extension adds fields UID and PHOTO to vCard provided by default
*It also provides image compression that mostly could be useful when sending contact request
*/
enum VCardFields: String {
case begin = "BEGIN:VCARD"
case uid = "UID:"
case photoJPEG = "PHOTO;TYPE=JPEG;ENCODING=BASE64:"
case photoPNG = "PHOTO;TYPE=PNG;ENCODING=BASE64:"
case end = "END:VCARD"
}
extension CNContactVCardSerialization {
class func dataWithImageAndUUID(from contact: CNContact, andImageCompression compressedSize: Int?) throws -> Data {
var vcData = try CNContactVCardSerialization.data(with: [contact])
guard var vcString = String(data: vcData, encoding: String.Encoding.utf8) else {
return vcData
}
let entryUID = VCardFields.uid.rawValue + contact.identifier
vcString = vcString.replacingOccurrences(of: VCardFields.begin.rawValue,
with: (VCardFields.begin.rawValue +
"\n" + entryUID))
guard var image = contact.imageData else {
vcData = vcString.data(using: .utf8)!
return vcData
}
var photofieldName = VCardFields.photoPNG
// if we need smallest image first scale it and than compress
var scaledImage: UIImage?
if compressedSize != nil {
scaledImage = UIImage(data: image)?.convert(toSize: CGSize(width:50.0, height:50.0), scale: 1)
}
if let scaledImage = scaledImage {
if UIImagePNGRepresentation(scaledImage) != nil {
image = UIImagePNGRepresentation(scaledImage)!
}
}
if let compressionSize = compressedSize, image.count > compressionSize {
// compress image before sending vCard
guard let compressedImage = UIImage(data: image)?.convertToData(ofMaxSize: compressionSize)else {
vcData = vcString.data(using: .utf8)!
return vcData
}
image = compressedImage
photofieldName = VCardFields.photoJPEG
}
let base64Image = image.base64EncodedString(options: Data.Base64EncodingOptions.init(rawValue: 0))
let vcardImageString = photofieldName.rawValue + base64Image + "\n"
vcString = vcString.replacingOccurrences(of: VCardFields.end.rawValue, with: (vcardImageString + VCardFields.end.rawValue))
vcData = vcString.data(using: .utf8)!
return vcData
}
}
......@@ -45,4 +45,20 @@ extension UIImage {
return copied!
}
func convertToData(ofMaxSize maxSize: Int) -> Data? {
guard let imageData = UIImageJPEGRepresentation(self, 1) else {
return nil
}
var fileSize = imageData.count
var i = 10
while fileSize > maxSize && i >= 0 {
guard let imageData = UIImageJPEGRepresentation(self, CGFloat(0.1 * Double(i))) else {
return nil
}
fileSize = imageData.count
i -= 1
}
return imageData
}
}
......@@ -61,7 +61,7 @@ class ContactRequestsViewModel: ViewModel {
let acceptCompleted = self.contactsService.accept(contactRequest: item.contactRequest, withAccount: self.accountsService.currentAccount!)
if let vCard = item.contactRequest.vCard {
let saveVCardCompleted = self.contactsService.saveVCard(vCard: vCard, withName: item.contactRequest.ringId)
let saveVCardCompleted = self.contactsService.saveVCard(vCard: vCard, forContactWithRingId: item.contactRequest.ringId)
return Observable<Void>.zip(acceptCompleted, saveVCardCompleted) { _, _ in
return
}
......
......@@ -54,6 +54,10 @@ class MeViewController: UIViewController, UIImagePickerControllerDelegate, UINav
.bind(to: self.ringIdLabel.rx.text)
.disposed(by: disposeBag)
self.viewModel.image?.asObservable()
.bind(to: self.profileImageView.rx.image)
.disposed(by: disposeBag)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
profileImageView.isUserInteractionEnabled = true
profileImageView.addGestureRecognizer(tapGestureRecognizer)
......@@ -103,8 +107,10 @@ class MeViewController: UIViewController, UIImagePickerControllerDelegate, UINav
image = img
}
image = image.convert(toSize:CGSize(width:100.0, height:100.0), scale: UIScreen.main.scale)
self.viewModel.saveProfile(withImage: image)
profileImageView.contentMode = .scaleAspectFit
profileImageView.image = image.convert(toSize:CGSize(width:100.0, height:100.0), scale: UIScreen.main.scale).circleMasked
profileImageView.image = image.circleMasked
dismiss(animated:true, completion: nil)
}
......
......@@ -29,6 +29,7 @@ class MeViewModel: Stateable, ViewModel {
let accountService: AccountsService
var userName: Single<String?>
let ringId: Single<String?>
var image: Single<Image?>?
lazy var state: Observable<State> = {
return self.stateSubject.asObservable()
......@@ -38,6 +39,31 @@ class MeViewModel: Stateable, ViewModel {
self.accountService = injectionBag.accountService
self.userName = Single.just(self.accountService.currentAccount?.volatileDetails?.get(withConfigKeyModel: ConfigKeyModel(withKey: ConfigKey.accountRegisteredName)))
self.ringId = Single.just(self.accountService.currentAccount?.details?.get(withConfigKeyModel: ConfigKeyModel(withKey: .accountUsername)))
guard let account = self.accountService.currentAccount else {
return
}
let disposebag = DisposeBag()
self.accountService.loadVCard(forAccounr: account)
.subscribe(onSuccess: { card in
if let data = card.imageData {
self.image = Single.just(UIImage(data: data)?.convert(toSize:CGSize(width:100.0, height:100.0), scale: UIScreen.main.scale).circleMasked)
} else {
self.image = Single.just(nil)
}
}).disposed(by: disposebag)
}
func saveProfile(withImage image: UIImage) {
let vcard = CNMutableContact()
vcard.imageData = UIImagePNGRepresentation(image)
vcard.familyName = (self.accountService.currentAccount!
.volatileDetails!.get(withConfigKeyModel: ConfigKeyModel(withKey: ConfigKey.accountRegisteredName)))
self.accountService
.saveVCard(vCard: vcard, forAccounr: self.accountService.currentAccount!)
.subscribe()
}
}
......@@ -378,4 +378,16 @@ class AccountsService: AccountAdapterDelegate {
self.responseStream.onNext(event)
}
// MARK: - profile
func saveVCard(vCard: CNContact, forAccounr account: AccountModel) -> Observable<Void> {
let vCardSaved = VCardUtils.saveVCard(vCard: vCard, withName: account.id, inFolder: VCardFolders.profile.rawValue)
return vCardSaved
}
func loadVCard(forAccounr account: AccountModel) -> Single<CNContact> {
let vCardSaved = VCardUtils.loadVCard(named: account.id, inFolder: VCardFolders.profile.rawValue)
return vCardSaved
}
}
......@@ -159,43 +159,6 @@ class ContactsService {
}
}
func loadVCard(named name: String) -> Single<CNContact> {
return Single.create(subscribe: { single in
if let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
do {
if let data = FileManager.default.contents(atPath: directoryURL.appendingPathComponent(name).absoluteString) {
let vCard = try CNContactVCardSerialization.contacts(with: data).first!
single(.success(vCard))
}
} catch {
single(.error(ContactServiceError.loadVCardFailed))
}
} else {
single(.error(ContactServiceError.loadVCardFailed))
}
return Disposables.create { }
})
}
func saveVCard(vCard: CNContact, withName name: String) -> Observable<Void> {
return Observable.create { observable in
if let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
do {
let data = try CNContactVCardSerialization.data(with: [vCard])
try data.write(to: directoryURL.appendingPathComponent(name))
observable.on(.completed)
} catch {
observable.on(.error(ContactServiceError.saveVCardFailed))
}
} else {
observable.on(.error(ContactServiceError.saveVCardFailed))
}
return Disposables.create { }
}
}
fileprivate func removeContactRequest(withRingId ringId: String) {
guard let contactRequestToRemove = self.contactRequests.value.filter({ $0.ringId == ringId}).first else {
return
......@@ -277,4 +240,16 @@ extension ContactsService: ContactsAdapterDelegate {
self.removeContact(withRingId: uri)
log.debug("Contact removed :\(uri)")
}
// 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 loadVCard(forContactWithRingId ringID: String) -> Single<CNContact> {
let vCardSaved = VCardUtils.loadVCard(named:ringID, inFolder: VCardFolders.contacts.rawValue)
return vCardSaved
}
}
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