diff --git a/.gitignore b/.gitignore
index 330d1674..2008b1b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,17 +2,11 @@
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
-## User settings
-xcuserdata/
-
-## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
-*.xcscmblueprint
-*.xccheckout
-
-## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
+## Build generated
build/
DerivedData/
-*.moved-aside
+
+## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
@@ -21,65 +15,44 @@ DerivedData/
!default.mode2v3
*.perspectivev3
!default.perspectivev3
+xcuserdata/
+
+## Other
+*.moved-aside
+*.xccheckout
+*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
-
-## App packaging
*.ipa
*.dSYM.zip
*.dSYM
-## Playgrounds
-timeline.xctimeline
-playground.xcworkspace
-
-# Swift Package Manager
-#
-# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
-# Packages/
-# Package.pins
-# Package.resolved
-# *.xcodeproj
-#
-# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
-# hence it is not needed unless you have added a package configuration file to your project
-# .swiftpm
-
-.build/
-
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
-# Pods/
-#
-# Add this line if you want to avoid checking in source code from the Xcode workspace
-# *.xcworkspace
+Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
-Carthage/Build/
-
-# Accio dependency management
-Dependencies/
-.accio/
+Carthage/Build
# fastlane
#
-# It is recommended to not store the screenshots in the git repo.
-# Instead, use fastlane to re-generate the screenshots whenever they are needed.
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
+# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
-fastlane/screenshots/**/*.png
+fastlane/screenshots
fastlane/test_output
# Code Injection
@@ -88,3 +61,6 @@ fastlane/test_output
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
+
+# General
+.DS_Store
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..0cebda29
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,20 @@
+
+Copyright (c) 2018 Related Code - http://relatedcode.com
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+1. This License grants you, the purchaser, an ongoing, non-exclusive, worldwide license to make use of Premium Chat.
+2. You are licensed to use Premium Chat to create one single End Product for yourself or for one client, and the End Product may be Sold.
+3. You can create the End Product for a client, and this license is then transferred from you to your client.
+4. You can Sell and make any number of copies of the single End Product.
+5. You can modify or manipulate Premium Chat. You can combine Premium Chat with other works and make a derivative work from it. The resulting works are subject to the terms of this license.
+6. This license is a “single application” license and not a “multi-use” license, which means that you can’t use Premium Chat to create more than one unique End Product.
+7. You can’t re-distribute Premium Chat with source files. You can’t do this with Premium Chat either on its own or bundled with other items, and even if you modify Premium Chat. You can’t re-distribute or make available Premium Chat as-is or with superficial modifications.
+8. This license can be terminated if you breach it. If that happens, you must stop making copies of or distributing the End Product until you remove Premium Chat from it.
+9. The author of Premium Chat retains ownership of Premium Chat but grants you the license on these terms. This license is between the author of Premium Chat and you.
diff --git a/Messenger/Classes/Chat/ChatGroupView.swift b/Messenger/Classes/Chat/ChatGroupView.swift
new file mode 100644
index 00000000..4130f285
--- /dev/null
+++ b/Messenger/Classes/Chat/ChatGroupView.swift
@@ -0,0 +1,11 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
diff --git a/Messenger/Classes/Chat/ChatPrivateView.swift b/Messenger/Classes/Chat/ChatPrivateView.swift
new file mode 100644
index 00000000..dfe02a38
--- /dev/null
+++ b/Messenger/Classes/Chat/ChatPrivateView.swift
@@ -0,0 +1,741 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class ChatPrivateView: RCMessagesView, UIGestureRecognizerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, AudioDelegate, StickersDelegate, SelectUsersDelegate {
+
+ private var recipientId = ""
+ private var chatId = ""
+ private var isBlocker = false
+
+ private var dbmessages: RLMResults = DBMessage.objects(with: NSPredicate(value: false))
+ private var rcmessages: [String: RCMessage] = [:]
+ private var avatarImages: [String: UIImage] = [:]
+ private var avatarIds: [String] = []
+
+ private var insertCounter: Int = 0
+ private var typingCounter: Int = 0
+ private var lastRead: Int64 = 0
+
+ private var firebase1: DatabaseReference?
+ private var firebase2: DatabaseReference?
+
+ private var timer: Timer?
+ private var indexForward: IndexPath?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func myInit(recipientId recipientId_: String) {
+
+ recipientId = recipientId_
+
+ chatId = Chat.chatId(recipientId: recipientId)
+ isBlocker = Blocker.isBlocker(userId: recipientId)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+
+ navigationController?.interactivePopGestureRecognizer?.delegate = self
+
+ let buttonBack = UIBarButtonItem(image: UIImage(named: "chat_back"), style: .plain, target: self, action: #selector(actionBack))
+ let buttonCallAudio = UIBarButtonItem(image: UIImage(named: "chat_callaudio"), style: .plain, target: self, action: #selector(actionCallAudio))
+ let buttonCallVideo = UIBarButtonItem(image: UIImage(named: "chat_callvideo"), style: .plain, target: self, action: #selector(actionCallVideo))
+
+ navigationItem.leftBarButtonItem = buttonBack
+ navigationItem.rightBarButtonItems = [buttonCallVideo, buttonCallAudio]
+
+ if (isBlocker) {
+ navigationItem.rightBarButtonItems = nil
+ buttonInputAttach.isUserInteractionEnabled = false
+ buttonInputSend.isUserInteractionEnabled = false
+ textInput.isUserInteractionEnabled = false
+ }
+
+ let wallpaper = FUser.wallpaper()
+ if (wallpaper.count != 0) {
+ tableView.backgroundView = UIImageView(image: UIImage(named: wallpaper))
+ }
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTableView1), name: NOTIFICATION_REFRESH_MESSAGES1)
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTableView2), name: NOTIFICATION_REFRESH_MESSAGES2)
+
+ insertCounter = Int(INSERT_MESSAGES)
+
+ loadMessages()
+ refreshTableView2()
+
+ typingIndicatorObserver()
+ createLastReadObservers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ timer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(updateTitleDetails), userInfo: nil, repeats: true)
+
+ Messages.assignChatId(chatId: chatId)
+
+ Status.updateLastRead(chatId: chatId)
+
+ updateTitleDetails()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidDisappear(_ animated: Bool) {
+
+ super.viewDidDisappear(animated)
+
+ timer?.invalidate()
+ timer = nil
+
+ if (isMovingFromParent) {
+ actionCleanup()
+ }
+ }
+
+ // MARK: - Realm methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadMessages() {
+
+ let predicate = NSPredicate(format: "chatId == %@ AND isDeleted == NO", chatId)
+ dbmessages = DBMessage.objects(with: predicate).sortedResults(usingKeyPath: FMESSAGE_CREATEDAT, ascending: true)
+ }
+
+ // MARK: - DBMessage methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func index(_ indexPath: IndexPath) -> Int {
+
+ let count = min(insertCounter, Int(dbmessages.count))
+ let offset = Int(dbmessages.count) - count
+
+ return (indexPath.section + offset)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func dbmessage(_ indexPath: IndexPath) -> DBMessage {
+
+ let index = self.index(indexPath)
+ return dbmessages[UInt(index)] as! DBMessage
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func dbmessageAbove(_ indexPath: IndexPath) -> DBMessage? {
+
+ if (indexPath.section > 0) {
+ let indexAbove = IndexPath(row: 0, section: indexPath.section-1)
+ return dbmessage(indexAbove)
+ }
+ return nil
+ }
+
+ // MARK: - Message methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func rcmessage(_ indexPath: IndexPath) -> RCMessage {
+
+ let dbmessage = self.dbmessage(indexPath)
+ let messageId = dbmessage.objectId
+
+ if (rcmessages[messageId] == nil) {
+
+ var rcmessage: RCMessage!
+ let incoming = (dbmessage.senderId != FUser.currentId())
+
+ if (dbmessage.type == MESSAGE_STATUS) {
+ rcmessage = RCMessage(status: dbmessage.text)
+ }
+
+ if (dbmessage.type == MESSAGE_TEXT) {
+ rcmessage = RCMessage(text: dbmessage.text, incoming: incoming)
+ }
+
+ if (dbmessage.type == MESSAGE_EMOJI) {
+ rcmessage = RCMessage(emoji: dbmessage.text, incoming: incoming)
+ }
+
+ if (dbmessage.type == MESSAGE_PICTURE) {
+ rcmessage = RCMessage(picture: nil, width: dbmessage.pictureWidth, height: dbmessage.pictureHeight, incoming: incoming)
+ MediaLoader.loadPicture(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ }
+
+ if (dbmessage.type == MESSAGE_VIDEO) {
+ rcmessage = RCMessage(video: nil, duration: dbmessage.videoDuration, incoming: incoming)
+ MediaLoader.loadVideo(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ }
+
+ if (dbmessage.type == MESSAGE_AUDIO) {
+ rcmessage = RCMessage(audio: nil, duration: dbmessage.audioDuration, incoming: incoming)
+ MediaLoader.loadAudio(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ }
+
+ if (dbmessage.type == MESSAGE_LOCATION) {
+ rcmessage = RCMessage(latitude: dbmessage.latitude, longitude: dbmessage.longitude, incoming: incoming) {
+ self.tableView.reloadData()
+ }
+ }
+
+ rcmessages[messageId] = rcmessage
+ }
+
+ return rcmessages[messageId]!
+ }
+
+ // MARK: - Avatar methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func avatarInitials(_ indexPath: IndexPath) -> String {
+
+ let dbmessage = self.dbmessage(indexPath)
+ return dbmessage.senderInitials
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func avatarImage(_ indexPath: IndexPath) -> UIImage? {
+
+ let dbmessage = self.dbmessage(indexPath)
+
+ if (avatarImages[dbmessage.senderId] == nil) {
+ loadAvatarImage(dbmessage)
+ }
+
+ return avatarImages[dbmessage.senderId]
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadAvatarImage(_ dbmessage: DBMessage) {
+
+ let userId = dbmessage.senderId
+
+ if (avatarIds.contains(userId)) { return } else { avatarIds.append(userId) }
+
+ DownloadManager.image(link: dbmessage.senderPicture) { path, error, network in
+ if (error == nil) {
+ if let image = UIImage(contentsOfFile: path!) {
+ self.avatarImages[userId] = image
+ }
+ DispatchQueue.main.async {
+ self.tableView.reloadData()
+ }
+ } else if ((error! as NSError).code != 100) {
+ if let index = self.avatarIds.index(of: userId) {
+ self.avatarIds.remove(at: index)
+ }
+ }
+ }
+ }
+
+ // MARK: - Header, Footer methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func textSectionHeader(_ indexPath: IndexPath) -> String? {
+
+ if (indexPath.section % 3 == 0) {
+ let dbmessage = self.dbmessage(indexPath)
+ let date = Date.date(timestamp: dbmessage.createdAt)
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "dd MMMM, HH:mm"
+ return dateFormatter.string(from: date)
+ } else {
+ return nil
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func textBubbleHeader(_ indexPath: IndexPath) -> String? {
+
+ let rcmessage = self.rcmessage(indexPath)
+ if (rcmessage.incoming) {
+ let dbmessage = self.dbmessage(indexPath)
+ if let dbmessageAbove = self.dbmessageAbove(indexPath) {
+ if (dbmessage.senderId == dbmessageAbove.senderId) {
+ return nil
+ }
+ }
+ return dbmessage.senderName
+ }
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func textBubbleFooter(_ indexPath: IndexPath) -> String? {
+
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func textSectionFooter(_ indexPath: IndexPath) -> String? {
+
+ let rcmessage = self.rcmessage(indexPath)
+ if (rcmessage.outgoing) {
+ let dbmessage = self.dbmessage(indexPath)
+ return (dbmessage.createdAt > lastRead) ? dbmessage.status : TEXT_READ
+ }
+ return nil
+ }
+
+ // MARK: - Menu controller methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func menuItems(_ indexPath: IndexPath) -> [Any]? {
+
+ let menuItemCopy = RCMenuItem(title: "Copy", action: #selector(actionMenuCopy(_:)))
+ let menuItemSave = RCMenuItem(title: "Save", action: #selector(actionMenuSave(_:)))
+ let menuItemDelete = RCMenuItem(title: "Delete", action: #selector(actionMenuDelete(_:)))
+ let menuItemForward = RCMenuItem(title: "Forward", action: #selector(actionMenuForward(_:)))
+
+ menuItemCopy.indexPath = indexPath
+ menuItemSave.indexPath = indexPath
+ menuItemDelete.indexPath = indexPath
+ menuItemForward.indexPath = indexPath
+
+ let rcmessage = self.rcmessage(indexPath)
+
+ var array: [RCMenuItem] = []
+
+ if (rcmessage.type == RC_TYPE_TEXT) { array.append(menuItemCopy) }
+ if (rcmessage.type == RC_TYPE_EMOJI) { array.append(menuItemCopy) }
+
+ if (rcmessage.type == RC_TYPE_PICTURE) { array.append(menuItemSave) }
+ if (rcmessage.type == RC_TYPE_VIDEO) { array.append(menuItemSave) }
+ if (rcmessage.type == RC_TYPE_AUDIO) { array.append(menuItemSave) }
+
+ array.append(menuItemDelete)
+ array.append(menuItemForward)
+
+ return array
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
+
+ if (action == #selector(actionMenuCopy(_:))) { return true }
+ if (action == #selector(actionMenuSave(_:))) { return true }
+ if (action == #selector(actionMenuDelete(_:))) { return true }
+ if (action == #selector(actionMenuForward(_:))) { return true }
+
+ return false
+ }
+
+ // MARK: - Typing indicator methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func typingIndicatorObserver() {
+
+ firebase1 = Database.database().reference(withPath: FTYPING_PATH).child(chatId)
+ firebase1?.observe(DataEventType.childChanged, with: { snapshot in
+ if (snapshot.key != FUser.currentId()) {
+
+ let typing = snapshot.value as! Bool
+ self.typingIndicatorShow(typing, animated: true)
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func typingIndicatorUpdate() {
+
+ typingCounter += 1
+ typingIndicatorSave(true)
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
+ self.typingIndicatorStop()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func typingIndicatorStop() {
+
+ typingCounter -= 1
+ if (typingCounter == 0) {
+ typingIndicatorSave(false)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func typingIndicatorSave(_ typing: Bool) {
+
+ firebase1?.updateChildValues([FUser.currentId(): typing])
+ }
+
+ // MARK: - LastRead methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func createLastReadObservers() {
+
+ firebase2 = Database.database().reference(withPath: FLASTREAD_PATH).child(chatId)
+ firebase2?.observe(DataEventType.value, with: { snapshot in
+ if (snapshot.exists()) {
+ let dictionary = snapshot.value as! [String: Any]
+ for userId in dictionary.keys {
+ if (userId != FUser.currentId()) {
+ let lastTemp = dictionary[userId] as! Int64
+ if (lastTemp > self.lastRead) {
+ self.lastRead = lastTemp
+ }
+ }
+ }
+ self.tableView.reloadData()
+ }
+ })
+ }
+
+ // MARK: - Title details methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func updateTitleDetails() {
+
+ let predicate = NSPredicate(format: "objectId == %@", recipientId)
+ let dbuser = DBUser.objects(with: predicate).firstObject() as! DBUser
+
+ labelTitle1.text = dbuser.fullname
+ labelTitle2.text = UserLastActive(dbuser: dbuser)
+ }
+
+ // MARK: - Refresh methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func refreshTableView1() {
+
+ refreshTableView2()
+ scroll(toBottom: true)
+ Status.updateLastRead(chatId: chatId)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func refreshTableView2() {
+
+ let show = (insertCounter < dbmessages.count)
+ loadEarlierShow(show)
+
+ tableView.reloadData()
+ }
+
+ // MARK: - Message send methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func messageSend(_ text: String?, picture: UIImage?, video: URL?, audio: String?) {
+
+ MessageQueue.send(chatId: chatId, recipientId: recipientId, status: nil, text: text, picture: picture, video: video, audio: audio)
+
+ Shortcut.update(userId: recipientId)
+ }
+
+ // MARK: - Message delete methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func messageDelete(_ indexPath: IndexPath) {
+
+ let dbmessage = self.dbmessage(indexPath)
+ Message.deleteItem(dbmessage: dbmessage)
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionBack() {
+
+ navigationController?.popViewController(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func actionTitle() {
+
+ let profileView = ProfileView()
+ profileView.myInit(userId: recipientId, chat: false)
+ navigationController?.pushViewController(profileView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCallAudio() {
+
+ AdvertPremium(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCallVideo() {
+
+ AdvertPremium(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func actionAttachMessage() {
+
+ view.endEditing(true)
+
+ let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+ let alertCamera = UIAlertAction(title: "Camera", style: .default, handler: { action in
+ PresentMultiCamera(target: self, edit: true)
+ })
+ let alertPicture = UIAlertAction(title: "Picture", style: .default, handler: { action in
+ PresentPhotoLibrary(target: self, edit: true)
+ })
+ let alertVideo = UIAlertAction(title: "Video", style: .default, handler: { action in
+ PresentVideoLibrary(target: self, edit: true)
+ })
+ let alertAudio = UIAlertAction(title: "Audio", style: .default, handler: { action in
+ self.actionAudio()
+ })
+ let alertStickers = UIAlertAction(title: "Sticker", style: .default, handler: { action in
+ self.actionStickers()
+ })
+ let alertLocation = UIAlertAction(title: "Location", style: .default, handler: { action in
+ self.actionLocation()
+ })
+
+ alertCamera.setValue(UIImage(named: "chat_camera"), forKey: "image"); alert.addAction(alertCamera)
+ alertPicture.setValue(UIImage(named: "chat_picture"), forKey: "image"); alert.addAction(alertPicture)
+ alertVideo.setValue(UIImage(named: "chat_video"), forKey: "image"); alert.addAction(alertVideo)
+ alertAudio.setValue(UIImage(named: "chat_audio"), forKey: "image"); alert.addAction(alertAudio)
+ alertStickers.setValue(UIImage(named: "chat_sticker"), forKey: "image"); alert.addAction(alertStickers)
+ alertLocation.setValue(UIImage(named: "chat_location"), forKey: "image"); alert.addAction(alertLocation)
+
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+
+ present(alert, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func actionSendMessage(_ text: String) {
+
+ messageSend(text, picture: nil, video: nil, audio: nil)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionAudio() {
+
+ let audioView = AudioView()
+ audioView.delegate = self
+ let navController = NavigationController(rootViewController: audioView)
+ present(navController, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionStickers() {
+
+ let stickersView = StickersView()
+ stickersView.delegate = self
+ let navController = NavigationController(rootViewController: stickersView)
+ present(navController, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionLocation() {
+
+ messageSend(nil, picture: nil, video: nil, audio: nil)
+ }
+
+ // MARK: - UIImagePickerControllerDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
+
+ let video = info[.mediaURL] as? URL
+ let picture = info[.editedImage] as? UIImage
+
+ messageSend(nil, picture: picture, video: video, audio: nil)
+
+ picker.dismiss(animated: true)
+ }
+
+ // MARK: - AudioDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func didRecordAudio(path: String) {
+
+ messageSend(nil, picture: nil, video: nil, audio: path)
+ }
+
+ // MARK: - StickersDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func didSelectSticker(sticker: String) {
+
+ let picture = UIImage(named: sticker)
+ messageSend(nil, picture: picture, video: nil, audio: nil)
+ }
+
+ // MARK: - User actions (load earlier)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func actionLoadEarlier() {
+
+ insertCounter += Int(INSERT_MESSAGES)
+ refreshTableView2()
+ }
+
+ // MARK: - User actions (bubble tap)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func actionTapBubble(_ indexPath: IndexPath) {
+
+ let dbmessage = self.dbmessage(indexPath)
+ let rcmessage = self.rcmessage(indexPath)
+
+ if (rcmessage.type == RC_TYPE_STATUS) {
+
+ }
+
+ if (rcmessage.type == RC_TYPE_PICTURE) {
+ if (rcmessage.status == RC_STATUS_MANUAL) {
+ MediaLoader.loadPictureManual(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ tableView.reloadData()
+ }
+ if (rcmessage.status == RC_STATUS_SUCCEED) {
+ let dictionary = PictureView.photos(messageId: dbmessage.objectId, chatId: chatId)
+ let photoItems = dictionary["photoItems"] as! [NYTPhoto]
+ let initialPhoto = dictionary["initialPhoto"] as! NYTPhoto
+
+ let pictureView = PictureView(photos: photoItems, initialPhoto: initialPhoto)
+ pictureView.setMessages(messages: true)
+ present(pictureView, animated: true)
+ }
+ }
+
+ if (rcmessage.type == RC_TYPE_VIDEO) {
+ if (rcmessage.status == RC_STATUS_MANUAL) {
+ MediaLoader.loadVideoManual(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ tableView.reloadData()
+ }
+ if (rcmessage.status == RC_STATUS_SUCCEED) {
+ let url = URL(fileURLWithPath: rcmessage.video_path)
+ let videoView = VideoView()
+ videoView.myInit(url: url)
+ present(videoView, animated: true)
+ }
+ }
+
+ if (rcmessage.type == RC_TYPE_AUDIO) {
+ if (rcmessage.status == RC_STATUS_MANUAL) {
+ MediaLoader.loadAudioManual(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ tableView.reloadData()
+ }
+ if (rcmessage.status == RC_STATUS_SUCCEED) {
+ if (rcmessage.audio_status == RC_AUDIOSTATUS_STOPPED) {
+ if let sound = Sound(contentsOfFile: rcmessage.audio_path) {
+ sound.completionHandler = { didFinish in
+ rcmessage.audio_status = Int(RC_AUDIOSTATUS_STOPPED)
+ self.tableView.reloadData()
+ }
+ SoundManager.shared().playSound(sound)
+ rcmessage.audio_status = Int(RC_AUDIOSTATUS_PLAYING)
+ tableView.reloadData()
+ }
+ } else if (rcmessage.audio_status == RC_AUDIOSTATUS_PLAYING) {
+ SoundManager.shared().stopAllSounds(false)
+ rcmessage.audio_status = Int(RC_AUDIOSTATUS_STOPPED)
+ tableView.reloadData()
+ }
+ }
+ }
+
+ if (rcmessage.type == RC_TYPE_LOCATION) {
+ let location = CLLocation(latitude: rcmessage.latitude, longitude: rcmessage.longitude)
+ let mapView = MapView()
+ mapView.myInit(location: location)
+ let navController = NavigationController(rootViewController: mapView)
+ present(navController, animated: true)
+ }
+ }
+
+ // MARK: - User actions (avatar tap)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func actionTapAvatar(_ indexPath: IndexPath) {
+
+ let dbmessage = self.dbmessage(indexPath)
+ let senderId = dbmessage.senderId
+
+ if (senderId != FUser.currentId()) {
+ let profileView = ProfileView()
+ profileView.myInit(userId: senderId, chat: false)
+ navigationController?.pushViewController(profileView, animated: true)
+ }
+ }
+
+ // MARK: - User actions (menu)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionMenuCopy(_ sender: Any?) {
+
+ if let indexPath = RCMenuItem.indexPath(sender as! UIMenuController) {
+ let rcmessage = self.rcmessage(indexPath)
+ UIPasteboard.general.string = rcmessage.text
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionMenuSave(_ sender: Any?) {
+
+ if let indexPath = RCMenuItem.indexPath(sender as! UIMenuController) {
+ let rcmessage = self.rcmessage(indexPath)
+
+ if (rcmessage.type == RC_TYPE_PICTURE) {
+ if (rcmessage.status == RC_STATUS_SUCCEED) {
+ UIImageWriteToSavedPhotosAlbum(rcmessage.picture_image!, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
+ }
+ }
+
+ if (rcmessage.type == RC_TYPE_VIDEO) {
+ if (rcmessage.status == RC_STATUS_SUCCEED) {
+ UISaveVideoAtPathToSavedPhotosAlbum(rcmessage.video_path, self, #selector(video(_:didFinishSavingWithError:contextInfo:)), nil)
+ }
+ }
+
+ if (rcmessage.type == RC_TYPE_AUDIO) {
+ if (rcmessage.status == RC_STATUS_SUCCEED) {
+ let path = File.temp(ext: "mp4")
+ File.copy(src: rcmessage.audio_path, dest: path, overwrite: true)
+ UISaveVideoAtPathToSavedPhotosAlbum(path, self, #selector(video(_:didFinishSavingWithError:contextInfo:)), nil)
+ }
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionMenuDelete(_ sender: Any?) {
+
+ if let indexPath = RCMenuItem.indexPath(sender as! UIMenuController) {
+ messageDelete(indexPath)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionMenuForward(_ sender: Any?) {
+
+ AdvertPremium(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeMutableRawPointer?) {
+
+ if (error != nil) { ProgressHUD.showError("Saving failed.") } else { ProgressHUD.showSuccess("Successfully saved.") }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func video(_ videoPath: String, didFinishSavingWithError error: NSError?, contextInfo: UnsafeMutableRawPointer?) {
+
+ if (error != nil) { ProgressHUD.showError("Saving failed.") } else { ProgressHUD.showSuccess("Successfully saved.") }
+ }
+
+ // MARK: - SelectUsersDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func didSelectUsers(users: [DBUser]) {
+
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func numberOfSections(in tableView: UITableView) -> Int {
+
+ return min(insertCounter, Int(dbmessages.count))
+ }
+
+ // MARK: - Cleanup methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionCleanup() {
+
+ Messages.resignChatId()
+
+ typingIndicatorSave(false)
+
+ firebase1?.removeAllObservers(); firebase1 = nil
+ firebase2?.removeAllObservers(); firebase2 = nil
+
+ NotificationCenterX.removeObserver(target: self)
+ }
+}
diff --git a/Messenger/Classes/Chat/DialogflowView.swift b/Messenger/Classes/Chat/DialogflowView.swift
new file mode 100644
index 00000000..5b0b3915
--- /dev/null
+++ b/Messenger/Classes/Chat/DialogflowView.swift
@@ -0,0 +1,223 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DialogflowView: RCMessagesView {
+
+ var rcmessages: [RCMessage] = []
+
+ private var apiAI: ApiAI?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(actionDone))
+
+ buttonInputAttach.isUserInteractionEnabled = false
+
+ let wallpaper = FUser.wallpaper()
+ if (wallpaper.count != 0) {
+ tableView.backgroundView = UIImageView(image: UIImage(named: wallpaper))
+ }
+
+ apiAI = ApiAI.shared()
+
+ loadEarlierShow(false)
+ updateTitleDetails()
+ }
+
+ // MARK: - Message methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func rcmessage(_ indexPath: IndexPath) -> RCMessage {
+
+ return rcmessages[indexPath.section]
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func addMessage(text: String, incoming: Bool) {
+
+ let rcmessage = RCMessage(text: text, incoming: incoming)
+ rcmessages.append(rcmessage)
+ refreshTableView1()
+ }
+
+ // MARK: - Avatar methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func avatarInitials(_ indexPath: IndexPath) -> String {
+
+ let rcmessage = self.rcmessage(indexPath)
+
+ if (rcmessage.outgoing) {
+ return FUser.initials()
+ } else {
+ return "AI"
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func avatarImage(_ indexPath: IndexPath) -> UIImage? {
+
+ return nil
+ }
+
+ // MARK: - Header, Footer methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func textSectionHeader(_ indexPath: IndexPath) -> String? {
+
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func textBubbleHeader(_ indexPath: IndexPath) -> String? {
+
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func textBubbleFooter(_ indexPath: IndexPath) -> String? {
+
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func textSectionFooter(_ indexPath: IndexPath) -> String? {
+
+ return nil
+ }
+
+ // MARK: - Menu controller methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func menuItems(_ indexPath: IndexPath) -> [Any]? {
+
+ let menuItemCopy = RCMenuItem(title: "Copy", action: #selector(actionMenuCopy(_:)))
+
+ menuItemCopy.indexPath = indexPath
+
+ return [menuItemCopy]
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
+
+ if (action == #selector(actionMenuCopy(_:))) { return true }
+ return false
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override var canBecomeFirstResponder: Bool {
+
+ return true
+ }
+
+ // MARK: - Typing indicator methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func typingIndicatorShow(_ show: Bool, animated: Bool, delay: CGFloat) {
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + Double(delay)) {
+ self.typingIndicatorShow(show, animated: animated)
+ }
+ }
+
+ // MARK: - Title details methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateTitleDetails() {
+
+ labelTitle1.text = "AI interface"
+ labelTitle2.text = "online now"
+ }
+
+ // MARK: - Refresh methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func refreshTableView1() {
+
+ refreshTableView2()
+ scroll(toBottom: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func refreshTableView2() {
+
+ tableView.reloadData()
+ }
+
+ // MARK: - Dialogflow methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func sendDialogflowRequest(text: String) {
+
+ typingIndicatorShow(true, animated: true, delay: 0.5)
+
+ let aiRequest: AITextRequest? = apiAI?.textRequest()
+ aiRequest?.query = [text]
+
+ aiRequest?.setCompletionBlockSuccess({ request, response in
+ self.typingIndicatorShow(false, animated: true, delay: 1.0)
+ self.displayDialogflowResponse(dictionary: response as! [AnyHashable: Any], delay: 1.1)
+ }, failure: { request, error in
+ ProgressHUD.showError("Dialogflow request error.")
+ })
+
+ apiAI?.enqueue(aiRequest)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func displayDialogflowResponse(dictionary: [AnyHashable: Any], delay: CGFloat) {
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + Double(delay)) {
+ self.displayDialogflowResponse(dictionary: dictionary)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func displayDialogflowResponse(dictionary: [AnyHashable: Any]) {
+
+ let result = dictionary["result"] as! [AnyHashable: Any]
+ let fulfillment = result["fulfillment"] as! [AnyHashable: Any]
+ let text = fulfillment["speech"] as! String
+
+ addMessage(text: text, incoming: true)
+ Audio.playMessageIncoming()
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionDone() {
+
+ dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func actionSendMessage(_ text: String) {
+
+ Audio.playMessageOutgoing()
+ addMessage(text: text, incoming: false)
+
+ sendDialogflowRequest(text: text)
+ }
+
+ // MARK: - User actions (menu)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionMenuCopy(_ sender: Any?) {
+
+ if let indexPath = RCMenuItem.indexPath(sender as! UIMenuController) {
+ let rcmessage = self.rcmessage(indexPath)
+ UIPasteboard.general.string = rcmessage.text
+ }
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func numberOfSections(in tableView: UITableView) -> Int {
+
+ return rcmessages.count
+ }
+}
diff --git a/Messenger/Classes/Details/01_Profile/ProfileView.swift b/Messenger/Classes/Details/01_Profile/ProfileView.swift
new file mode 100644
index 00000000..6b780adf
--- /dev/null
+++ b/Messenger/Classes/Details/01_Profile/ProfileView.swift
@@ -0,0 +1,297 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class ProfileView: UIViewController, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var tableView: UITableView!
+ @IBOutlet var viewHeader: UIView!
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var labelName: UILabel!
+ @IBOutlet var labelDetails: UILabel!
+ @IBOutlet var cellStatus: UITableViewCell!
+ @IBOutlet var cellCountry: UITableViewCell!
+ @IBOutlet var cellLocation: UITableViewCell!
+ @IBOutlet var cellPhone: UITableViewCell!
+ @IBOutlet var buttonCallPhone: UIButton!
+ @IBOutlet var cellMedia: UITableViewCell!
+ @IBOutlet var cellChat: UITableViewCell!
+ @IBOutlet var cellFriend: UITableViewCell!
+ @IBOutlet var cellBlock: UITableViewCell!
+
+ private var userId = ""
+ private var isBlocker = false
+ private var isChatEnabled = false
+ private var timer: Timer?
+ private var dbuser: DBUser!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func myInit(userId userId_: String, chat chat_: Bool) {
+
+ userId = userId_
+ isChatEnabled = chat_
+
+ isBlocker = Blocker.isBlocker(userId: userId)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Profile"
+
+ navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
+
+ tableView.tableHeaderView = viewHeader
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ loadUser()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ timer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(loadUser), userInfo: nil, repeats: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+
+ timer?.invalidate()
+ timer = nil
+ }
+
+ // MARK: - Realm methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func loadUser() {
+
+ let predicate = NSPredicate(format: "objectId == %@", userId)
+ dbuser = DBUser.objects(with: predicate).firstObject() as? DBUser
+
+ labelInitials.text = dbuser.initials()
+ DownloadManager.image(link: dbuser.picture) { path, error, network in
+ if (error == nil) {
+ self.imageUser.image = UIImage(contentsOfFile: path!)
+ self.labelInitials.text = nil
+ }
+ }
+
+ labelName.text = dbuser.fullname
+ labelDetails.text = UserLastActive(dbuser: dbuser)
+
+ cellStatus.detailTextLabel?.text = dbuser.status
+ cellCountry.detailTextLabel?.text = dbuser.country
+ cellLocation.detailTextLabel?.text = dbuser.location
+
+ buttonCallPhone.setTitle(dbuser.phone, for: .normal)
+
+ cellFriend.textLabel?.text = Friend.isFriend(userId: userId) ? "Remove Friend" : "Add Friend"
+ cellBlock.textLabel?.text = Blocked.isBlocked(userId: userId) ? "Unblock User" : "Block User"
+
+ tableView.reloadData()
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionPhoto(_ sender: Any) {
+
+ if let picture = imageUser.image {
+ let photoItems = PictureView.photos(picture: picture)
+ let pictureView = PictureView(photos: photoItems)
+ present(pictureView, animated: true)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionCallPhone(_ sender: Any) {
+
+ let number1 = "tel://\(dbuser.phone)"
+ let number2 = number1.replacingOccurrences(of: " ", with: "")
+
+ if let url = URL(string: number2) {
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionCallAudio(_ sender: Any) {
+
+ AdvertPremium(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionCallVideo(_ sender: Any) {
+
+ AdvertPremium(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionMedia() {
+
+ let recipientId = dbuser.objectId
+ let chatId = Chat.chatId(recipientId: recipientId)
+
+ let allMediaView = AllMediaView()
+ allMediaView.myInit(chatId: chatId)
+ navigationController?.pushViewController(allMediaView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionChatPrivate() {
+
+ let chatPrivateView = ChatPrivateView()
+ chatPrivateView.myInit(recipientId: dbuser.objectId)
+ navigationController?.pushViewController(chatPrivateView, animated: true)
+ }
+
+ // MARK: - User actions (Friend/Unfriend)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionFriendOrUnfriend() {
+
+ Friend.isFriend(userId: userId) ? actionUnfriend() : actionFriend()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionFriend() {
+
+ let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+ alert.addAction(UIAlertAction(title: "Add Friend", style: .default, handler: { action in
+ self.actionFriendUser()
+ }))
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+
+ present(alert, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionFriendUser() {
+
+ Friend.createItem(userId: userId)
+ cellFriend.textLabel?.text = "Remove Friend"
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionUnfriend() {
+
+ let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+ alert.addAction(UIAlertAction(title: "Remove Friend", style: .default, handler: { action in
+ self.actionUnfriendUser()
+ }))
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+
+ present(alert, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionUnfriendUser() {
+
+ Friend.deleteItem(userId: userId)
+ cellFriend.textLabel?.text = "Add Friend"
+ }
+
+ // MARK: - User actions (Block/Unblock)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionBlockOrUnblock() {
+
+ Blocked.isBlocked(userId: userId) ? actionUnblock() : actionBlock()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionBlock() {
+
+ let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+ alert.addAction(UIAlertAction(title: "Block User", style: .destructive, handler: { action in
+ self.actionBlockUser()
+ }))
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+
+ present(alert, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionBlockUser() {
+
+ AdvertPremium(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionUnblock() {
+
+ let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+ alert.addAction(UIAlertAction(title: "Unblock User", style: .destructive, handler: { action in
+ self.actionUnblockUser()
+ }))
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+
+ present(alert, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionUnblockUser() {
+
+ AdvertPremium(target: self);
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 3
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ if (section == 0) { return isBlocker ? 3 : 4 }
+ if (section == 1) { return isChatEnabled ? 2 : 1 }
+ if (section == 2) { return 2 }
+
+ return 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { return cellStatus }
+ if (indexPath.section == 0) && (indexPath.row == 1) { return cellCountry }
+ if (indexPath.section == 0) && (indexPath.row == 2) { return cellLocation }
+ if (indexPath.section == 0) && (indexPath.row == 3) { return cellPhone }
+ if (indexPath.section == 1) && (indexPath.row == 0) { return cellMedia }
+ if (indexPath.section == 1) && (indexPath.row == 1) { return cellChat }
+ if (indexPath.section == 2) && (indexPath.row == 0) { return cellFriend }
+ if (indexPath.section == 2) && (indexPath.row == 1) { return cellBlock }
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ if (indexPath.section == 1) && (indexPath.row == 0) { actionMedia() }
+ if (indexPath.section == 1) && (indexPath.row == 1) { actionChatPrivate() }
+ if (indexPath.section == 2) && (indexPath.row == 0) { actionFriendOrUnfriend() }
+ if (indexPath.section == 2) && (indexPath.row == 1) { actionBlockOrUnblock() }
+ }
+}
diff --git a/Messenger/Classes/Details/01_Profile/ProfileView.xib b/Messenger/Classes/Details/01_Profile/ProfileView.xib
new file mode 100644
index 00000000..4c7055ad
--- /dev/null
+++ b/Messenger/Classes/Details/01_Profile/ProfileView.xib
@@ -0,0 +1,331 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Details/02_Group/GroupView.swift b/Messenger/Classes/Details/02_Group/GroupView.swift
new file mode 100644
index 00000000..4130f285
--- /dev/null
+++ b/Messenger/Classes/Details/02_Group/GroupView.swift
@@ -0,0 +1,11 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
diff --git a/Messenger/Classes/Details/02_Group/GroupView.xib b/Messenger/Classes/Details/02_Group/GroupView.xib
new file mode 100755
index 00000000..a9cd9dc4
--- /dev/null
+++ b/Messenger/Classes/Details/02_Group/GroupView.xib
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Details/03_AllMedia/AllMediaCell.swift b/Messenger/Classes/Details/03_AllMedia/AllMediaCell.swift
new file mode 100644
index 00000000..0065a06b
--- /dev/null
+++ b/Messenger/Classes/Details/03_AllMedia/AllMediaCell.swift
@@ -0,0 +1,58 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class AllMediaCell: UICollectionViewCell {
+
+ @IBOutlet var imageItem: UIImageView!
+ @IBOutlet var imageVideo: UIImageView!
+ @IBOutlet var imageSelected: UIImageView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(dbmessage: DBMessage, selected: Bool) {
+
+ imageItem.image = UIImage(named: "allmedia_blank")
+ imageVideo.isHidden = (dbmessage.type == MESSAGE_PICTURE)
+
+ if (dbmessage.type == MESSAGE_PICTURE) {
+ bindPicture(dbmessage: dbmessage)
+ }
+
+ if (dbmessage.type == MESSAGE_VIDEO) {
+ bindVideo(dbmessage: dbmessage)
+ }
+
+ imageSelected.isHidden = (selected == false)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindPicture(dbmessage: DBMessage) {
+
+ if let path = DownloadManager.pathImage(link: dbmessage.picture) {
+ if let image = UIImage(contentsOfFile: path) {
+ imageItem.image = Image.square(image: image, size: 320)
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindVideo(dbmessage: DBMessage) {
+
+ if let path = DownloadManager.pathVideo(link: dbmessage.video) {
+ DispatchQueue(label: "bindVideo").async {
+ let image = Video.thumbnail(path: path)
+ DispatchQueue.main.async {
+ self.imageItem.image = Image.square(image: image, size: 320)
+ }
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Details/03_AllMedia/AllMediaCell.xib b/Messenger/Classes/Details/03_AllMedia/AllMediaCell.xib
new file mode 100755
index 00000000..ee9eec0e
--- /dev/null
+++ b/Messenger/Classes/Details/03_AllMedia/AllMediaCell.xib
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Details/03_AllMedia/AllMediaHeader.swift b/Messenger/Classes/Details/03_AllMedia/AllMediaHeader.swift
new file mode 100644
index 00000000..8571725c
--- /dev/null
+++ b/Messenger/Classes/Details/03_AllMedia/AllMediaHeader.swift
@@ -0,0 +1,22 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class AllMediaHeader: UICollectionReusableView {
+
+ @IBOutlet var label: UILabel!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func awakeFromNib() {
+
+ super.awakeFromNib()
+ }
+}
diff --git a/Messenger/Classes/Details/03_AllMedia/AllMediaHeader.xib b/Messenger/Classes/Details/03_AllMedia/AllMediaHeader.xib
new file mode 100644
index 00000000..822ccb22
--- /dev/null
+++ b/Messenger/Classes/Details/03_AllMedia/AllMediaHeader.xib
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Details/03_AllMedia/AllMediaView.swift b/Messenger/Classes/Details/03_AllMedia/AllMediaView.swift
new file mode 100644
index 00000000..b663b9e4
--- /dev/null
+++ b/Messenger/Classes/Details/03_AllMedia/AllMediaView.swift
@@ -0,0 +1,272 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class AllMediaView: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, NYTPhotosViewControllerDelegate {
+
+ @IBOutlet var collectionView: UICollectionView!
+ @IBOutlet var viewFooter: UIView!
+ @IBOutlet var labelFooter: UILabel!
+ @IBOutlet var buttonShare: UIBarButtonItem!
+ @IBOutlet var buttonDelete: UIBarButtonItem!
+
+ private var chatId = ""
+ private var selection: [String] = []
+ private var dbmessages_media: [DBMessage] = []
+
+ private var months: [String] = []
+ private var dictionary: [String: [DBMessage]] = [:]
+
+ private var isSelecting = false
+ private var buttonDone: UIBarButtonItem?
+ private var buttonSelect: UIBarButtonItem?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func myInit(chatId chatId_: String) {
+
+ chatId = chatId_
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "All Media"
+
+ buttonDone = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+ buttonSelect = UIBarButtonItem(title: "Select", style: .plain, target: self, action: #selector(actionSelect))
+
+ collectionView.register(UINib(nibName: "AllMediaHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "AllMediaHeader")
+ collectionView.register(UINib(nibName: "AllMediaCell", bundle: nil), forCellWithReuseIdentifier: "AllMediaCell")
+
+ updateDetails()
+ loadMedia()
+ }
+
+ // MARK: - Load methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadMedia() {
+
+ months.removeAll()
+ dictionary.removeAll()
+ dbmessages_media.removeAll()
+
+ var pictures: Int = 0
+ var videos: Int = 0
+
+ let predicate = NSPredicate(format: "chatId == %@ AND isDeleted == NO", chatId)
+ let dbmessages = DBMessage.objects(with: predicate).sortedResults(usingKeyPath: FMESSAGE_CREATEDAT, ascending: true)
+
+ for i in 0.. Int {
+
+ return months.count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+
+ let month = months[section]
+ if let dbmessages_section = dictionary[month] {
+ return dbmessages_section.count
+ }
+ return 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
+
+ if (kind == UICollectionView.elementKindSectionHeader) {
+ let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "AllMediaHeader", for: indexPath) as! AllMediaHeader
+ header.label.text = months[indexPath.section]
+ return header
+ }
+ return UICollectionReusableView()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+
+ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AllMediaCell", for: indexPath) as! AllMediaCell
+
+ let month = months[indexPath.section]
+ if let dbmessages_section = dictionary[month] {
+ let dbmessage = dbmessages_section[indexPath.item]
+ let selected = selection.contains(dbmessage.objectId)
+ cell.bindData(dbmessage: dbmessage, selected: selected)
+ }
+
+ return cell
+ }
+
+ // MARK: - UICollectionViewDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+
+ collectionView.deselectItem(at: indexPath, animated: true)
+
+ let month = months[indexPath.section]
+ if let dbmessages_section = dictionary[month] {
+ let dbmessage = dbmessages_section[indexPath.item]
+
+ if (isSelecting == false) {
+ if (dbmessage.type == MESSAGE_PICTURE) {
+ showPicture(dbmessage: dbmessage)
+ }
+ if (dbmessage.type == MESSAGE_VIDEO) {
+ showVideo(dbmessage: dbmessage)
+ }
+ }
+
+ if (isSelecting == true) {
+ if selection.contains(dbmessage.objectId) {
+ if let index = selection.index(of: dbmessage.objectId) {
+ selection.remove(at: index)
+ }
+ } else {
+ selection.append(dbmessage.objectId)
+ }
+ collectionView.reloadItems(at: [indexPath])
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func showPicture(dbmessage: DBMessage) {
+
+ if let path = DownloadManager.pathImage(link: dbmessage.picture) {
+
+ let dictionary = PictureView.photos(messageId: dbmessage.objectId, chatId: chatId)
+ let photoItems = dictionary["photoItems"] as! [NYTPhoto]
+ let initialPhoto = dictionary["initialPhoto"] as! NYTPhoto
+
+ let pictureView = PictureView(photos: photoItems, initialPhoto: initialPhoto)
+ pictureView.delegate = self
+ pictureView.setMessages(messages: true)
+ present(pictureView, animated: true)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func showVideo(dbmessage: DBMessage) {
+
+ if let path = DownloadManager.pathVideo(link: dbmessage.video) {
+ let url = URL(fileURLWithPath: path)
+ let videoView = VideoView()
+ videoView.myInit(url: url)
+ present(videoView, animated: true)
+ }
+ }
+
+ // MARK: - NYTPhotosViewControllerDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func photosViewControllerWillDismiss(_ photosViewController: NYTPhotosViewController) {
+
+ loadMedia()
+ }
+}
diff --git a/Messenger/Classes/Details/03_AllMedia/AllMediaView.xib b/Messenger/Classes/Details/03_AllMedia/AllMediaView.xib
new file mode 100755
index 00000000..4681a725
--- /dev/null
+++ b/Messenger/Classes/Details/03_AllMedia/AllMediaView.xib
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Details/04_Picture/PictureView.swift b/Messenger/Classes/Details/04_Picture/PictureView.swift
new file mode 100644
index 00000000..8abaf13d
--- /dev/null
+++ b/Messenger/Classes/Details/04_Picture/PictureView.swift
@@ -0,0 +1,191 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class PictureView: NYTPhotosViewController, SelectUsersDelegate {
+
+ private var isMessages = false
+ private var statusBarIsHidden = false
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func photos(picture: UIImage) -> [NYTPhoto] {
+
+ let photoItem = NYTPhotoItem()
+ photoItem.image = picture
+ return [photoItem]
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func photos(messageId: String, chatId: String) -> [String: Any] {
+
+ var photoItems: [NYTPhotoItem] = []
+ var initialPhoto: NYTPhotoItem? = nil
+
+ let predicate = NSPredicate(format: "chatId == %@ AND type == %@ AND isDeleted == NO", chatId, MESSAGE_PICTURE)
+ let dbmessages = DBMessage.objects(with: predicate).sortedResults(usingKeyPath: FMESSAGE_CREATEDAT, ascending: true)
+
+ let attributesTitle = [NSAttributedString.Key.foregroundColor: UIColor.white,
+ NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body)]
+ let attributesCredit = [NSAttributedString.Key.foregroundColor: UIColor.gray,
+ NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)]
+
+ for i in 0..
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Details/06_Map/MapView.swift b/Messenger/Classes/Details/06_Map/MapView.swift
new file mode 100644
index 00000000..87716959
--- /dev/null
+++ b/Messenger/Classes/Details/06_Map/MapView.swift
@@ -0,0 +1,56 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class MapView: UIViewController {
+
+ @IBOutlet var mapView: MKMapView!
+
+ private var location: CLLocation!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func myInit(location location_: CLLocation) {
+
+ location = location_
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Map"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ var region: MKCoordinateRegion = MKCoordinateRegion()
+ region.center.latitude = location.coordinate.latitude
+ region.center.longitude = location.coordinate.longitude
+ region.span.latitudeDelta = CLLocationDegrees(0.01)
+ region.span.longitudeDelta = CLLocationDegrees(0.01)
+ mapView.setRegion(region, animated: false)
+
+ let annotation = MKPointAnnotation()
+ mapView.addAnnotation(annotation)
+ annotation.coordinate = location.coordinate
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCancel() {
+
+ dismiss(animated: true)
+ }
+}
diff --git a/Messenger/Classes/Details/06_Map/MapView.xib b/Messenger/Classes/Details/06_Map/MapView.xib
new file mode 100644
index 00000000..d278b004
--- /dev/null
+++ b/Messenger/Classes/Details/06_Map/MapView.xib
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Misc/Audio/AudioView.swift b/Messenger/Classes/Misc/Audio/AudioView.swift
new file mode 100644
index 00000000..64d7bd42
--- /dev/null
+++ b/Messenger/Classes/Misc/Audio/AudioView.swift
@@ -0,0 +1,210 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc protocol AudioDelegate: class {
+
+ func didRecordAudio(path: String)
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class AudioView: UIViewController, AVAudioPlayerDelegate {
+
+ @IBOutlet weak var delegate: AudioDelegate?
+
+ @IBOutlet var labelTimer: UILabel!
+ @IBOutlet var buttonRecord: UIButton!
+ @IBOutlet var buttonStop: UIButton!
+ @IBOutlet var buttonDelete: UIButton!
+ @IBOutlet var buttonPlay: UIButton!
+ @IBOutlet var buttonSend: UIButton!
+
+ private var isPlaying = false
+ private var isRecorded = false
+ private var isRecording = false
+
+ private var timer: Timer?
+ private var dateTimer: Date?
+
+ private var audioPlayer: AVAudioPlayer?
+ private var audioRecorder: AVAudioRecorder?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Audio"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+
+ updateButtonDetails()
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCancel() {
+
+ actionStop(0)
+
+ dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionRecord(_ sender: Any) {
+
+ audioRecorderStart()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionStop(_ sender: Any) {
+
+ if (isPlaying) { audioPlayerStop() }
+ if (isRecording) { audioRecorderStop() }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionDelete(_ sender: Any) {
+
+ isRecorded = false
+ updateButtonDetails()
+
+ timerReset()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionPlay(_ sender: Any) {
+
+ audioPlayerStart()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionSend(_ sender: Any) {
+
+ dismiss(animated: true)
+
+ if let path = audioRecorder?.url.path {
+ delegate?.didRecordAudio(path: path)
+ }
+ }
+
+ // MARK: - Audio recorder methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func audioRecorderStart() {
+
+ isRecording = true
+ updateButtonDetails()
+
+ timerStart()
+
+ try? AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, policy: .default, options: .defaultToSpeaker)
+
+ let settings = [AVFormatIDKey: kAudioFormatMPEG4AAC, AVSampleRateKey: 44100, AVNumberOfChannelsKey: 2]
+ audioRecorder = try? AVAudioRecorder(url: URL(fileURLWithPath: File.temp(ext: "m4a")), settings: settings)
+ audioRecorder?.prepareToRecord()
+ audioRecorder?.record()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func audioRecorderStop() {
+
+ isRecording = false
+ isRecorded = true
+ updateButtonDetails()
+
+ timerStop()
+
+ audioRecorder?.stop()
+ }
+
+ // MARK: - Audio player methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func audioPlayerStart() {
+
+ isPlaying = true
+ updateButtonDetails()
+
+ timerStart()
+
+ try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, policy: .default, options: .defaultToSpeaker)
+
+ if let url = audioRecorder?.url {
+ audioPlayer = try? AVAudioPlayer(contentsOf: url)
+ audioPlayer?.delegate = self
+ audioPlayer?.prepareToPlay()
+ audioPlayer?.play()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
+
+ isPlaying = false
+ updateButtonDetails()
+
+ timerStop()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func audioPlayerStop() {
+
+ isPlaying = false
+ updateButtonDetails()
+
+ timerStop()
+
+ audioPlayer?.stop()
+ }
+
+ // MARK: - Timer methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func timerStart() {
+
+ dateTimer = Date()
+
+ timer = Timer.scheduledTimer(timeInterval: 0.07, target: self, selector: #selector(timerUpdate), userInfo: nil, repeats: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func timerUpdate() {
+
+ if (dateTimer != nil) {
+ let interval = Date().timeIntervalSince(dateTimer!)
+ let millisec = Int(interval * 100) % 100
+ let seconds = Int(interval) % 60
+ let minutes = Int(interval) / 60
+ labelTimer.text = String(format: "%02d:%02d:%02d", minutes, seconds, millisec)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func timerStop() {
+
+ timer?.invalidate()
+ timer = nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func timerReset() {
+
+ labelTimer.text = "00:00:00"
+ }
+
+ // MARK: - Helper methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateButtonDetails() {
+
+ buttonRecord.isHidden = isRecorded
+ buttonStop.isHidden = (isPlaying == false) && (isRecording == false)
+ buttonDelete.isHidden = (isPlaying == true) || (isRecorded == false)
+ buttonPlay.isHidden = (isPlaying == true) || (isRecorded == false)
+ buttonSend.isHidden = (isPlaying == true) || (isRecorded == false)
+ }
+}
diff --git a/Messenger/Classes/Misc/Audio/AudioView.xib b/Messenger/Classes/Misc/Audio/AudioView.xib
new file mode 100644
index 00000000..eb4cdc11
--- /dev/null
+++ b/Messenger/Classes/Misc/Audio/AudioView.xib
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ CourierNewPS-BoldMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Misc/CallAudio/CallAudioView.swift b/Messenger/Classes/Misc/CallAudio/CallAudioView.swift
new file mode 100644
index 00000000..4130f285
--- /dev/null
+++ b/Messenger/Classes/Misc/CallAudio/CallAudioView.swift
@@ -0,0 +1,11 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
diff --git a/Messenger/Classes/Misc/CallAudio/CallAudioView.xib b/Messenger/Classes/Misc/CallAudio/CallAudioView.xib
new file mode 100644
index 00000000..b8cdebec
--- /dev/null
+++ b/Messenger/Classes/Misc/CallAudio/CallAudioView.xib
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Misc/CallVideo/CallVideoView.swift b/Messenger/Classes/Misc/CallVideo/CallVideoView.swift
new file mode 100644
index 00000000..4130f285
--- /dev/null
+++ b/Messenger/Classes/Misc/CallVideo/CallVideoView.swift
@@ -0,0 +1,11 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
diff --git a/Messenger/Classes/Misc/CallVideo/CallVideoView.xib b/Messenger/Classes/Misc/CallVideo/CallVideoView.xib
new file mode 100644
index 00000000..e13e795c
--- /dev/null
+++ b/Messenger/Classes/Misc/CallVideo/CallVideoView.xib
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Misc/Countries/CountriesView.swift b/Messenger/Classes/Misc/Countries/CountriesView.swift
new file mode 100644
index 00000000..11ffc66f
--- /dev/null
+++ b/Messenger/Classes/Misc/Countries/CountriesView.swift
@@ -0,0 +1,123 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc protocol CountriesDelegate: class {
+
+ func didSelectCountry(name: String, code: String)
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class CountriesView: UIViewController, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet weak var delegate: CountriesDelegate?
+
+ @IBOutlet var tableView: UITableView!
+
+ private var countries: [[String: String]] = []
+ private var sections: [[[String: String]]] = []
+ private let collation = UILocalizedIndexedCollation.current()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Countries"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+
+ loadCountries()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadCountries() {
+
+ if let aCountries = NSArray(contentsOfFile: Dir.application("countries.plist")) {
+ countries = aCountries as! [[String: String]]
+ }
+
+ let selector: Selector = "name"
+ sections = Array(repeating: [], count: collation.sectionTitles.count)
+
+ let sorted = collation.sortedArray(from: countries, collationStringSelector: selector) as! [[String: String]]
+ for country in sorted {
+ let section = collation.section(for: country, collationStringSelector: selector)
+ sections[section].append(country)
+ }
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCancel() {
+
+ dismiss(animated: true)
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return sections.count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return sections[section].count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+
+ return (sections[section].count != 0) ? collation.sectionTitles[section] : nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func sectionIndexTitles(for tableView: UITableView) -> [String]? {
+
+ return collation.sectionIndexTitles
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
+
+ return collation.section(forSectionIndexTitle: index)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell")
+ if (cell == nil) { cell = UITableViewCell(style: .default, reuseIdentifier: "cell") }
+
+ let country = sections[indexPath.section][indexPath.row]
+ cell.textLabel?.text = country["name"]
+
+ return cell
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let country = sections[indexPath.section][indexPath.row]
+
+ if let name = country["name"] {
+ if let code = country["dial_code"] {
+ delegate?.didSelectCountry(name: name, code: code)
+ }
+ }
+
+ dismiss(animated: true)
+ }
+}
diff --git a/Messenger/Classes/Misc/Countries/CountriesView.xib b/Messenger/Classes/Misc/Countries/CountriesView.xib
new file mode 100644
index 00000000..b5d6fdaf
--- /dev/null
+++ b/Messenger/Classes/Misc/Countries/CountriesView.xib
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Misc/NavController/NavigationController.swift b/Messenger/Classes/Misc/NavController/NavigationController.swift
new file mode 100644
index 00000000..58a185a1
--- /dev/null
+++ b/Messenger/Classes/Misc/NavController/NavigationController.swift
@@ -0,0 +1,31 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class NavigationController: UINavigationController {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+
+ navigationBar.isTranslucent = false
+ navigationBar.barTintColor = UIColor.orange
+ navigationBar.tintColor = UIColor.white
+ navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override var preferredStatusBarStyle: UIStatusBarStyle {
+
+ return .lightContent
+ }
+}
diff --git a/Messenger/Classes/Misc/SelectUser/SelectUserCell.swift b/Messenger/Classes/Misc/SelectUser/SelectUserCell.swift
new file mode 100644
index 00000000..69bf5038
--- /dev/null
+++ b/Messenger/Classes/Misc/SelectUser/SelectUserCell.swift
@@ -0,0 +1,65 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class SelectUserCell: UITableViewCell {
+
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var labelName: UILabel!
+ @IBOutlet var labelStatus: UILabel!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(dbuser: DBUser) {
+
+ labelName.text = dbuser.fullname
+ labelStatus.text = dbuser.status
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadImage(dbuser: DBUser, tableView: UITableView, indexPath: IndexPath) {
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ if (dbuser.thumbnail != "") {
+ if let path = DownloadManager.pathImage(link: dbuser.thumbnail) {
+ imageUser.image = UIImage(contentsOfFile: path)
+ labelInitials.text = nil
+ } else {
+ imageUser.image = UIImage(named: "selectuser_blank")
+ labelInitials.text = dbuser.initials()
+ downloadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+ }
+ } else {
+ imageUser.image = UIImage(named: "selectuser_blank")
+ labelInitials.text = dbuser.initials()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func downloadImage(dbuser: DBUser, tableView: UITableView, indexPath: IndexPath) {
+
+ DownloadManager.image(link: dbuser.thumbnail) { path, error, network in
+ if (error == nil) {
+ if (tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false) {
+ let cell = tableView.cellForRow(at: indexPath) as! SelectUserCell
+ cell.imageUser.image = UIImage(contentsOfFile: path!)
+ cell.labelInitials.text = nil
+ }
+ } else if ((error! as NSError).code == 102) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ self.downloadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+ }
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Misc/SelectUser/SelectUserCell.xib b/Messenger/Classes/Misc/SelectUser/SelectUserCell.xib
new file mode 100644
index 00000000..7c76df06
--- /dev/null
+++ b/Messenger/Classes/Misc/SelectUser/SelectUserCell.xib
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Misc/SelectUser/SelectUserView.swift b/Messenger/Classes/Misc/SelectUser/SelectUserView.swift
new file mode 100644
index 00000000..71f4d1a1
--- /dev/null
+++ b/Messenger/Classes/Misc/SelectUser/SelectUserView.swift
@@ -0,0 +1,241 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc protocol SelectUserDelegate: class {
+
+ func didSelectUser(dbuser: DBUser)
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class SelectUserView: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet weak var delegate: SelectUserDelegate?
+
+ @IBOutlet var searchBar: UISearchBar!
+ @IBOutlet var tableView: UITableView!
+
+ private var blockerIds: [String] = []
+ private var friendIds: [String] = []
+ private var dbusers: RLMResults = DBUser.objects(with: NSPredicate(value: false))
+
+ private var sections: [[DBUser]] = []
+ private let collation = UILocalizedIndexedCollation.current()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Select User"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+
+ tableView.register(UINib(nibName: "SelectUserCell", bundle: nil), forCellReuseIdentifier: "SelectUserCell")
+
+ tableView.tableFooterView = UIView()
+
+ loadBlockers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+
+ dismissKeyboard()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func dismissKeyboard() {
+
+ view.endEditing(true)
+ }
+
+ // MARK: - Realm methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadBlockers() {
+
+ blockerIds.removeAll()
+
+ let predicate = NSPredicate(format: "isDeleted == NO")
+ let dbblockers = DBBlocker.objects(with: predicate)
+
+ for i in 0.. Int {
+
+ return sections.count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return sections[section].count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+
+ return (sections[section].count != 0) ? collation.sectionTitles[section] : nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func sectionIndexTitles(for tableView: UITableView) -> [String]? {
+
+ return collation.sectionIndexTitles
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
+
+ return collation.section(forSectionIndexTitle: index)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "SelectUserCell", for: indexPath) as! SelectUserCell
+
+ let dbuser = sections[indexPath.section][indexPath.row]
+
+ cell.bindData(dbuser: dbuser)
+ cell.loadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+
+ return cell
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ dismiss(animated: true) {
+ let dbuser = self.sections[indexPath.section][indexPath.row]
+ self.delegate?.didSelectUser(dbuser: dbuser)
+ }
+ }
+
+ // MARK: - UISearchBarDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+
+ loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidBeginEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(true, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidEndEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(false, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarCancelButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.text = ""
+ searchBar.resignFirstResponder()
+ loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarSearchButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.resignFirstResponder()
+ }
+}
diff --git a/Messenger/Classes/Misc/SelectUser/SelectUserView.xib b/Messenger/Classes/Misc/SelectUser/SelectUserView.xib
new file mode 100644
index 00000000..e6445ac0
--- /dev/null
+++ b/Messenger/Classes/Misc/SelectUser/SelectUserView.xib
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Misc/SelectUsers/SelectUsersCell.swift b/Messenger/Classes/Misc/SelectUsers/SelectUsersCell.swift
new file mode 100644
index 00000000..4b4a80c5
--- /dev/null
+++ b/Messenger/Classes/Misc/SelectUsers/SelectUsersCell.swift
@@ -0,0 +1,65 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class SelectUsersCell: UITableViewCell {
+
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var labelName: UILabel!
+ @IBOutlet var labelStatus: UILabel!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(dbuser: DBUser) {
+
+ labelName.text = dbuser.fullname
+ labelStatus.text = dbuser.status
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadImage(dbuser: DBUser, tableView: UITableView, indexPath: IndexPath) {
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ if (dbuser.thumbnail != "") {
+ if let path = DownloadManager.pathImage(link: dbuser.thumbnail) {
+ imageUser.image = UIImage(contentsOfFile: path)
+ labelInitials.text = nil
+ } else {
+ imageUser.image = UIImage(named: "selectusers_blank")
+ labelInitials.text = dbuser.initials()
+ downloadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+ }
+ } else {
+ imageUser.image = UIImage(named: "selectusers_blank")
+ labelInitials.text = dbuser.initials()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func downloadImage(dbuser: DBUser, tableView: UITableView, indexPath: IndexPath) {
+
+ DownloadManager.image(link: dbuser.thumbnail) { path, error, network in
+ if (error == nil) {
+ if (tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false) {
+ let cell = tableView.cellForRow(at: indexPath) as! SelectUsersCell
+ cell.imageUser.image = UIImage(contentsOfFile: path!)
+ cell.labelInitials.text = nil
+ }
+ } else if ((error! as NSError).code == 102) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ self.downloadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+ }
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Misc/SelectUsers/SelectUsersCell.xib b/Messenger/Classes/Misc/SelectUsers/SelectUsersCell.xib
new file mode 100644
index 00000000..ea2c3d99
--- /dev/null
+++ b/Messenger/Classes/Misc/SelectUsers/SelectUsersCell.xib
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Misc/SelectUsers/SelectUsersView.swift b/Messenger/Classes/Misc/SelectUsers/SelectUsersView.swift
new file mode 100644
index 00000000..dda1a036
--- /dev/null
+++ b/Messenger/Classes/Misc/SelectUsers/SelectUsersView.swift
@@ -0,0 +1,275 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc protocol SelectUsersDelegate: class {
+
+ func didSelectUsers(users: [DBUser])
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class SelectUsersView: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet weak var delegate: SelectUsersDelegate?
+
+ @IBOutlet var searchBar: UISearchBar!
+ @IBOutlet var tableView: UITableView!
+
+ private var blockerIds: [String] = []
+ private var friendIds: [String] = []
+ private var dbusers: RLMResults = DBUser.objects(with: NSPredicate(value: false))
+
+ private var selection: [String] = []
+ private var sections: [[DBUser]] = []
+ private let collation = UILocalizedIndexedCollation.current()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Select Users"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(actionDone))
+
+ tableView.register(UINib(nibName: "SelectUsersCell", bundle: nil), forCellReuseIdentifier: "SelectUsersCell")
+
+ tableView.tableFooterView = UIView()
+
+ loadBlockers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+
+ dismissKeyboard()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func dismissKeyboard() {
+
+ view.endEditing(true)
+ }
+
+ // MARK: - Realm methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadBlockers() {
+
+ blockerIds.removeAll()
+
+ let predicate = NSPredicate(format: "isDeleted == NO")
+ let dbblockers = DBBlocker.objects(with: predicate)
+
+ for i in 0.. Int {
+
+ return sections.count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return sections[section].count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+
+ return (sections[section].count != 0) ? collation.sectionTitles[section] : nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func sectionIndexTitles(for tableView: UITableView) -> [String]? {
+
+ return collation.sectionIndexTitles
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
+
+ return collation.section(forSectionIndexTitle: index)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "SelectUsersCell", for: indexPath) as! SelectUsersCell
+
+ let dbuser = sections[indexPath.section][indexPath.row]
+
+ cell.bindData(dbuser: dbuser)
+ cell.loadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+
+ cell.accessoryType = selection.contains(dbuser.objectId) ? .checkmark : .none
+
+ return cell
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let dbuser = sections[indexPath.section][indexPath.row]
+
+ if (selection.contains(dbuser.objectId)) {
+ if let index = selection.index(of: dbuser.objectId) {
+ selection.remove(at: index)
+ }
+ } else {
+ selection.append(dbuser.objectId)
+ }
+
+ let cell: UITableViewCell! = tableView.cellForRow(at: indexPath)
+ cell.accessoryType = selection.contains(dbuser.objectId) ? .checkmark : .none
+ }
+
+ // MARK: - UISearchBarDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+
+ loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidBeginEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(true, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidEndEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(false, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarCancelButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.text = ""
+ searchBar.resignFirstResponder()
+ loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarSearchButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.resignFirstResponder()
+ }
+}
diff --git a/Messenger/Classes/Misc/SelectUsers/SelectUsersView.xib b/Messenger/Classes/Misc/SelectUsers/SelectUsersView.xib
new file mode 100644
index 00000000..ba05ab18
--- /dev/null
+++ b/Messenger/Classes/Misc/SelectUsers/SelectUsersView.xib
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Misc/Stickers/StickersCell.swift b/Messenger/Classes/Misc/Stickers/StickersCell.swift
new file mode 100644
index 00000000..ede46a96
--- /dev/null
+++ b/Messenger/Classes/Misc/Stickers/StickersCell.swift
@@ -0,0 +1,22 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class StickersCell: UICollectionViewCell {
+
+ @IBOutlet var imageItem: UIImageView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(file: String) {
+
+ imageItem.image = UIImage(named: file)
+ }
+}
diff --git a/Messenger/Classes/Misc/Stickers/StickersCell.xib b/Messenger/Classes/Misc/Stickers/StickersCell.xib
new file mode 100755
index 00000000..d3c6a658
--- /dev/null
+++ b/Messenger/Classes/Misc/Stickers/StickersCell.xib
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Misc/Stickers/StickersView.swift b/Messenger/Classes/Misc/Stickers/StickersView.swift
new file mode 100644
index 00000000..7b8072b9
--- /dev/null
+++ b/Messenger/Classes/Misc/Stickers/StickersView.swift
@@ -0,0 +1,100 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc protocol StickersDelegate: class {
+
+ func didSelectSticker(sticker: String)
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class StickersView: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
+
+ @IBOutlet weak var delegate: StickersDelegate?
+
+ @IBOutlet var collectionView: UICollectionView!
+
+ private var stickers1: [String] = []
+ private var stickers2: [String] = []
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Stickers"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+
+ collectionView.register(UINib(nibName: "StickersCell", bundle: nil), forCellWithReuseIdentifier: "StickersCell")
+
+ loadStickers()
+ }
+
+ // MARK: - Load stickers
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadStickers() {
+
+ if let files = try? FileManager.default.contentsOfDirectory(atPath: Dir.application()) {
+ for file in files.sorted() {
+ if (file.contains("stickerlocal")) {
+ stickers1.append(file)
+ }
+ if (file.contains("stickersend")) {
+ stickers2.append(file)
+ }
+ }
+ }
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCancel() {
+
+ dismiss(animated: true)
+ }
+
+ // MARK: - UICollectionViewDataSource
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in collectionView: UICollectionView) -> Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+
+ return stickers1.count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+
+ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StickersCell", for: indexPath) as! StickersCell
+
+ cell.bindData(file: stickers1[indexPath.item])
+
+ return cell
+ }
+
+ // MARK: - UICollectionViewDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+
+ collectionView.deselectItem(at: indexPath, animated: true)
+
+ let file = stickers2[indexPath.item]
+ let sticker = file.replacingOccurrences(of: "@2x.png", with: "")
+
+ delegate?.didSelectSticker(sticker: sticker)
+
+ dismiss(animated: true)
+ }
+}
diff --git a/Messenger/Classes/Misc/Stickers/StickersView.xib b/Messenger/Classes/Misc/Stickers/StickersView.xib
new file mode 100755
index 00000000..52cb4774
--- /dev/null
+++ b/Messenger/Classes/Misc/Stickers/StickersView.xib
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/01_Chats/ChatsCell.swift b/Messenger/Classes/Tabs/01_Chats/ChatsCell.swift
new file mode 100644
index 00000000..4ff62e26
--- /dev/null
+++ b/Messenger/Classes/Tabs/01_Chats/ChatsCell.swift
@@ -0,0 +1,67 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class ChatsCell: MGSwipeTableCell {
+
+ @IBOutlet var viewUnread: UIView!
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var labelDescription: UILabel!
+ @IBOutlet var labelLastMessage: UILabel!
+ @IBOutlet var labelElapsed: UILabel!
+ @IBOutlet var imageMuted: UIImageView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(dbchat: DBChat) {
+
+ let lastRead = Status.lastRead(chatId: dbchat.chatId)
+ let mutedUntil = Status.mutedUntil(chatId: dbchat.chatId)
+
+ viewUnread.isHidden = (lastRead >= dbchat.lastIncoming)
+
+ labelDescription.text = dbchat.details
+ labelLastMessage.text = dbchat.lastMessage
+
+ labelElapsed.text = TimeElapsed(timestamp: dbchat.lastMessageDate)
+ imageMuted.isHidden = (mutedUntil < Date().timestamp())
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadImage(dbchat: DBChat, tableView: UITableView) {
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ if let path = DownloadManager.pathImage(link: dbchat.picture) {
+ imageUser.image = UIImage(contentsOfFile: path)
+ labelInitials.text = nil
+ } else {
+ imageUser.image = UIImage(named: "chats_blank")
+ labelInitials.text = dbchat.initials
+ downloadImage(dbchat: dbchat, tableView: tableView)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func downloadImage(dbchat: DBChat, tableView: UITableView) {
+
+ DownloadManager.image(link: dbchat.picture) { path, error, network in
+ if (error == nil) {
+ tableView.reloadData()
+ } else if ((error! as NSError).code == 102) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ self.downloadImage(dbchat: dbchat, tableView: tableView)
+ }
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Tabs/01_Chats/ChatsCell.xib b/Messenger/Classes/Tabs/01_Chats/ChatsCell.xib
new file mode 100644
index 00000000..4a61329a
--- /dev/null
+++ b/Messenger/Classes/Tabs/01_Chats/ChatsCell.xib
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/01_Chats/ChatsView.swift b/Messenger/Classes/Tabs/01_Chats/ChatsView.swift
new file mode 100644
index 00000000..ed09aa6b
--- /dev/null
+++ b/Messenger/Classes/Tabs/01_Chats/ChatsView.swift
@@ -0,0 +1,375 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class ChatsView: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate, MGSwipeTableCellDelegate, SelectUserDelegate {
+
+ @IBOutlet var searchBar: UISearchBar!
+ @IBOutlet var tableView: UITableView!
+
+ private var timer: Timer?
+ private var dbchats: RLMResults = DBChat.objects(with: NSPredicate(value: false))
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
+
+ super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
+
+ tabBarItem.image = UIImage(named: "tab_chats")
+ tabBarItem.title = "Chats"
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(actionCleanup), name: NOTIFICATION_USER_LOGGED_OUT)
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTableView), name: NOTIFICATION_USER_LOGGED_IN)
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTabCounter), name: NOTIFICATION_USER_LOGGED_IN)
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTableView), name: NOTIFICATION_REFRESH_CHATS)
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTabCounter), name: NOTIFICATION_REFRESH_CHATS)
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTableView), name: NOTIFICATION_REFRESH_STATUSES)
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTabCounter), name: NOTIFICATION_REFRESH_STATUSES)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ required init?(coder aDecoder: NSCoder) {
+
+ super.init(coder: aDecoder)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Chats"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(named: "chats_dialogflow"), style: .plain, target: self, action: #selector(actionDialogflow))
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(actionCompose))
+
+ timer = Timer.scheduledTimer(timeInterval: 30.0, target: self, selector: #selector(refreshTableView), userInfo: nil, repeats: true)
+
+ tableView.register(UINib(nibName: "ChatsCell", bundle: nil), forCellReuseIdentifier: "ChatsCell")
+
+ tableView.tableFooterView = UIView()
+
+ loadChats()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidAppear(_ animated: Bool) {
+
+ super.viewDidAppear(animated)
+
+ if (FUser.currentId() != "") {
+ if (FUser.isOnboardOk()) {
+ refreshTableView()
+ } else {
+ OnboardUser(target: self)
+ }
+ } else {
+ LoginUser(target: self)
+ }
+ }
+
+ // MARK: - Realm methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadChats() {
+
+ var predicate = NSPredicate(format: "isArchived == NO AND isDeleted == NO")
+
+ if let text = searchBar.text {
+ if (text.count != 0) {
+ predicate = NSPredicate(format: "isArchived == NO AND isDeleted == NO AND details CONTAINS[c] %@", text)
+ }
+ }
+
+ dbchats = DBChat.objects(with: predicate).sortedResults(usingKeyPath: "lastMessageDate", ascending: false)
+
+ refreshTableView()
+ refreshTabCounter()
+ }
+
+ // MARK: - Refresh methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func refreshTableView() {
+
+ tableView.reloadData()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func refreshTabCounter() {
+
+ var total: Int = 0
+
+ for i in 0.. Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return Int(dbchats.count)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "ChatsCell", for: indexPath) as! ChatsCell
+
+ cell.rightButtons = [MGSwipeButton(title: "Delete", backgroundColor: UIColor.red),
+ MGSwipeButton(title: "More", backgroundColor: UIColor.lightGray)]
+
+ cell.delegate = self
+ cell.tag = indexPath.row
+
+ let dbchat = dbchats[UInt(indexPath.row)] as! DBChat
+ cell.bindData(dbchat: dbchat)
+ cell.loadImage(dbchat: dbchat, tableView: tableView)
+
+ return cell
+ }
+
+ // MARK: - MGSwipeTableCellDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func swipeTableCell(_ cell: MGSwipeTableCell, tappedButtonAt index: Int, direction: MGSwipeDirection, fromExpansion: Bool) -> Bool {
+
+ if (index == 0) { actionDelete(index: cell.tag) }
+ if (index == 1) { actionMore(index: cell.tag) }
+
+ return true
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let dbchat = dbchats[UInt(indexPath.row)] as! DBChat
+
+ if (dbchat.groupId.count != 0) { actionChatGroup(groupId: dbchat.groupId) }
+ if (dbchat.recipientId.count != 0) { actionChatPrivate(recipientId: dbchat.recipientId) }
+ }
+
+ // MARK: - UISearchBarDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+
+ loadChats()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidBeginEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(true, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidEndEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(false, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarCancelButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.text = ""
+ searchBar.resignFirstResponder()
+ loadChats()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarSearchButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.resignFirstResponder()
+ }
+}
diff --git a/Messenger/Classes/Tabs/01_Chats/ChatsView.xib b/Messenger/Classes/Tabs/01_Chats/ChatsView.xib
new file mode 100644
index 00000000..b49b440f
--- /dev/null
+++ b/Messenger/Classes/Tabs/01_Chats/ChatsView.xib
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/02_Calls/CallsView.swift b/Messenger/Classes/Tabs/02_Calls/CallsView.swift
new file mode 100644
index 00000000..28a64fab
--- /dev/null
+++ b/Messenger/Classes/Tabs/02_Calls/CallsView.swift
@@ -0,0 +1,207 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class CallsView: UIViewController, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var tableView: UITableView!
+
+ private var timer: Timer?
+ private var dbcallhistories: RLMResults = DBCallHistory.objects(with: NSPredicate(value: false))
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
+
+ super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
+
+ tabBarItem.image = UIImage(named: "tab_calls")
+ tabBarItem.title = "Calls"
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(actionCleanup), name: NOTIFICATION_USER_LOGGED_OUT)
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTableView), name: NOTIFICATION_REFRESH_CALLHISTORIES)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ required init?(coder aDecoder: NSCoder) {
+
+ super.init(coder: aDecoder)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Calls"
+
+ navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Clear All", style: .plain, target: self, action: #selector(actionClearAll))
+
+ timer = Timer.scheduledTimer(timeInterval: 30.0, target: self, selector: #selector(refreshTableView), userInfo: nil, repeats: true)
+
+ tableView.tableFooterView = UIView()
+
+ loadCallHistories()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidAppear(_ animated: Bool) {
+
+ super.viewDidAppear(animated)
+
+ if (FUser.currentId() != "") {
+ if (FUser.isOnboardOk()) {
+ refreshTableView()
+ } else {
+ OnboardUser(target: self)
+ }
+ } else {
+ LoginUser(target: self)
+ }
+ }
+
+ // MARK: - Realm methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadCallHistories() {
+
+ let predicate = NSPredicate(format: "isDeleted == NO")
+ dbcallhistories = DBCallHistory.objects(with: predicate).sortedResults(usingKeyPath: FCALLHISTORY_CREATEDAT, ascending: false)
+
+ refreshTableView()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func deleteCallHistory(dbcallhistory: DBCallHistory) {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ dbcallhistory.isDeleted = true
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func deleteCallHistories() {
+
+ for i in 0.. Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return min(Int(dbcallhistories.count), 25)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell")
+ if (cell == nil) { cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell") }
+
+ let dbcallhistory = dbcallhistories[UInt(indexPath.row)] as! DBCallHistory
+ cell.textLabel?.text = dbcallhistory.text
+
+ cell.detailTextLabel?.text = dbcallhistory.status
+ cell.detailTextLabel?.textColor = UIColor.gray
+
+ let label = UILabel(frame: CGRect(x: 0, y: 0, width: 70, height: 50))
+ label.text = TimeElapsed(timestamp: dbcallhistory.startedAt)
+ label.textAlignment = .right
+ label.textColor = UIColor.gray
+ label.font = UIFont.systemFont(ofSize: 11)
+ cell.accessoryView = label
+
+ return cell
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
+
+ return true
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
+
+ let dbcallhistory = dbcallhistories[UInt(indexPath.row)] as! DBCallHistory
+
+ deleteCallHistory(dbcallhistory: dbcallhistory)
+
+ tableView.deleteRows(at: [indexPath], with: .fade)
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
+ CallHistory.deleteItem(objectId: dbcallhistory.objectId)
+ }
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let dbcallhistory = dbcallhistories[UInt(indexPath.row)] as! DBCallHistory
+ let userId = (dbcallhistory.recipientId == FUser.currentId()) ? dbcallhistory.initiatorId : dbcallhistory.recipientId
+
+ if (dbcallhistory.type == CALLHISTORY_AUDIO) { actionCallAudio(userId: userId) }
+ if (dbcallhistory.type == CALLHISTORY_VIDEO) { actionCallVideo(userId: userId) }
+ }
+}
diff --git a/Messenger/Classes/Tabs/02_Calls/CallsView.xib b/Messenger/Classes/Tabs/02_Calls/CallsView.xib
new file mode 100644
index 00000000..64d36ef3
--- /dev/null
+++ b/Messenger/Classes/Tabs/02_Calls/CallsView.xib
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsCell.swift b/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsCell.swift
new file mode 100644
index 00000000..715cffc6
--- /dev/null
+++ b/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsCell.swift
@@ -0,0 +1,65 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class AddFriendsCell: UITableViewCell {
+
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var labelName: UILabel!
+ @IBOutlet var labelStatus: UILabel!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(user: FUser) {
+
+ labelName.text = user[FUSER_FULLNAME] as? String
+ labelStatus.text = user[FUSER_STATUS] as? String
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadImage(user: FUser, tableView: UITableView, indexPath: IndexPath) {
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ if let thumbnail = user[FUSER_THUMBNAIL] as? String {
+ if let path = DownloadManager.pathImage(link: thumbnail) {
+ imageUser.image = UIImage(contentsOfFile: path)
+ labelInitials.text = nil
+ } else {
+ imageUser.image = UIImage(named: "addfriends_blank")
+ labelInitials.text = user.initials()
+ downloadImage(thumbnail: thumbnail, tableView: tableView, indexPath: indexPath)
+ }
+ } else {
+ imageUser.image = UIImage(named: "addfriends_blank")
+ labelInitials.text = user.initials()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func downloadImage(thumbnail: String, tableView: UITableView, indexPath: IndexPath) {
+
+ DownloadManager.image(link: thumbnail) { path, error, network in
+ if (error == nil) {
+ if (tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false) {
+ let cell = tableView.cellForRow(at: indexPath) as! AddFriendsCell
+ cell.imageUser.image = UIImage(contentsOfFile: path!)
+ cell.labelInitials.text = nil
+ }
+ } else if ((error! as NSError).code == 102) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ self.downloadImage(thumbnail: thumbnail, tableView: tableView, indexPath: indexPath)
+ }
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsCell.xib b/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsCell.xib
new file mode 100644
index 00000000..1a60f6c1
--- /dev/null
+++ b/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsCell.xib
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsView.swift b/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsView.swift
new file mode 100644
index 00000000..4127b1df
--- /dev/null
+++ b/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsView.swift
@@ -0,0 +1,224 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class AddFriendsView: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var searchBar: UISearchBar!
+ @IBOutlet var tableView: UITableView!
+
+ private var users: [FUser] = []
+ private var sections: [[FUser]] = []
+ private let collation = UILocalizedIndexedCollation.current()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Add Friends"
+
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(actionDone))
+
+ tableView.register(UINib(nibName: "AddFriendsCell", bundle: nil), forCellReuseIdentifier: "AddFriendsCell")
+
+ tableView.tableFooterView = UIView()
+
+ loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+
+ dismissKeyboard()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func dismissKeyboard() {
+
+ view.endEditing(true)
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadUsers() {
+
+ let firebase = Database.database().reference(withPath: FUSER_PATH)
+
+ firebase.observeSingleEvent(of: DataEventType.value, with: { snapshot in
+ self.users.removeAll()
+
+ if (snapshot.exists()) {
+ let dictionary = snapshot.value as! [String: Any]
+ for value in dictionary.values {
+ let temp = value as! [String: Any]
+ if (temp["fullname"] != nil) {
+ let user = FUser(path: FUSER_PATH, dictionary: temp)
+ self.users.append(user)
+ }
+ }
+ }
+
+ self.setObjects()
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func setObjects() {
+
+ sections.removeAll()
+
+ sections = Array(repeating: [], count: collation.sectionTitles.count)
+
+ let sorted = users.sorted(by: { $0.fullname() < $1.fullname() })
+ for user in sorted {
+
+ let fullname = user[FUSER_FULLNAME] as! NSString
+ let firstChar = fullname.substring(to: 1).uppercased()
+
+ if let index = collation.sectionTitles.index(of: firstChar) {
+ sections[index].append(user)
+ } else {
+ sections[collation.sectionTitles.endIndex-1].append(user)
+ }
+ }
+
+ refreshTableView()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func createFriend(user: FUser) {
+
+ let userId = user[FUSER_OBJECTID] as! String
+
+ if (Friend.isFriend(userId: userId) == false) {
+ Friend.createItem(userId: userId)
+ LinkedId.createItem(userId: userId)
+ ProgressHUD.showSuccess("Friend added.")
+ } else {
+ ProgressHUD.showSuccess("Already added.")
+ }
+ }
+
+ // MARK: - Refresh methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func refreshTableView() {
+
+ tableView.reloadData()
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionDone() {
+
+ dismiss(animated: true)
+ }
+
+ // MARK: - UIScrollViewDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+
+ dismissKeyboard()
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return sections.count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return sections[section].count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+
+ return (sections[section].count != 0) ? collation.sectionTitles[section] : nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func sectionIndexTitles(for tableView: UITableView) -> [String]? {
+
+ return collation.sectionIndexTitles
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
+
+ return collation.section(forSectionIndexTitle: index)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "AddFriendsCell", for: indexPath) as! AddFriendsCell
+
+ let user = sections[indexPath.section][indexPath.row]
+
+ cell.bindData(user: user)
+ cell.loadImage(user: user, tableView: tableView, indexPath: indexPath)
+
+ return cell
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let user = sections[indexPath.section][indexPath.row]
+
+ if (user.isCurrent() == false) {
+ createFriend(user: user)
+ } else {
+ ProgressHUD.showSuccess("This is you.")
+ }
+ }
+
+ // MARK: - UISearchBarDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+
+ //loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidBeginEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(true, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidEndEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(false, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarCancelButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.text = ""
+ searchBar.resignFirstResponder()
+ //loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarSearchButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.resignFirstResponder()
+ }
+}
diff --git a/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsView.xib b/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsView.xib
new file mode 100644
index 00000000..9500588d
--- /dev/null
+++ b/Messenger/Classes/Tabs/03_People/01_AddFriends/AddFriendsView.xib
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/03_People/PeopleCell.swift b/Messenger/Classes/Tabs/03_People/PeopleCell.swift
new file mode 100644
index 00000000..40e86c96
--- /dev/null
+++ b/Messenger/Classes/Tabs/03_People/PeopleCell.swift
@@ -0,0 +1,65 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class PeopleCell: UITableViewCell {
+
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var labelName: UILabel!
+ @IBOutlet var labelStatus: UILabel!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(dbuser: DBUser) {
+
+ labelName.text = dbuser.fullname
+ labelStatus.text = dbuser.status
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadImage(dbuser: DBUser, tableView: UITableView, indexPath: IndexPath) {
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ if (dbuser.thumbnail != "") {
+ if let path = DownloadManager.pathImage(link: dbuser.thumbnail) {
+ imageUser.image = UIImage(contentsOfFile: path)
+ labelInitials.text = nil
+ } else {
+ imageUser.image = UIImage(named: "people_blank")
+ labelInitials.text = dbuser.initials()
+ downloadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+ }
+ } else {
+ imageUser.image = UIImage(named: "people_blank")
+ labelInitials.text = dbuser.initials()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func downloadImage(dbuser: DBUser, tableView: UITableView, indexPath: IndexPath) {
+
+ DownloadManager.image(link: dbuser.thumbnail) { path, error, network in
+ if (error == nil) {
+ if (tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false) {
+ let cell = tableView.cellForRow(at: indexPath) as! PeopleCell
+ cell.imageUser.image = UIImage(contentsOfFile: path!)
+ cell.labelInitials.text = nil
+ }
+ } else if ((error! as NSError).code == 102) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ self.downloadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+ }
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Tabs/03_People/PeopleCell.xib b/Messenger/Classes/Tabs/03_People/PeopleCell.xib
new file mode 100644
index 00000000..ddb0f5f5
--- /dev/null
+++ b/Messenger/Classes/Tabs/03_People/PeopleCell.xib
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/03_People/PeopleView.swift b/Messenger/Classes/Tabs/03_People/PeopleView.swift
new file mode 100644
index 00000000..00898d21
--- /dev/null
+++ b/Messenger/Classes/Tabs/03_People/PeopleView.swift
@@ -0,0 +1,282 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class PeopleView: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var viewTitle: UIView!
+ @IBOutlet var labelTitle: UILabel!
+ @IBOutlet var searchBar: UISearchBar!
+ @IBOutlet var tableView: UITableView!
+
+ private var blockerIds: [String] = []
+ private var friendIds: [String] = []
+ private var dbusers: RLMResults = DBUser.objects(with: NSPredicate(value: false))
+
+ private var sections: [[DBUser]] = []
+ private let collation = UILocalizedIndexedCollation.current()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
+
+ super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
+
+ tabBarItem.image = UIImage(named: "tab_people")
+ tabBarItem.title = "People"
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(actionCleanup), name: NOTIFICATION_USER_LOGGED_OUT)
+ NotificationCenterX.addObserver(target: self, selector: #selector(loadBlockers), name: NOTIFICATION_USER_LOGGED_IN)
+ NotificationCenterX.addObserver(target: self, selector: #selector(loadBlockers), name: NOTIFICATION_REFRESH_BLOCKERS)
+ NotificationCenterX.addObserver(target: self, selector: #selector(loadFriends), name: NOTIFICATION_REFRESH_FRIENDS)
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTableView), name: NOTIFICATION_REFRESH_USERS)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ required init?(coder aDecoder: NSCoder) {
+
+ super.init(coder: aDecoder)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ navigationItem.titleView = viewTitle
+
+ navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(actionAddFriends))
+
+ tableView.register(UINib(nibName: "PeopleCell", bundle: nil), forCellReuseIdentifier: "PeopleCell")
+
+ tableView.tableFooterView = UIView()
+
+ if (FUser.currentId() != "") {
+ loadBlockers()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidAppear(_ animated: Bool) {
+
+ super.viewDidAppear(animated)
+
+ if (FUser.currentId() != "") {
+ if (FUser.isOnboardOk()) {
+ refreshTableView()
+ } else {
+ OnboardUser(target: self)
+ }
+ } else {
+ LoginUser(target: self)
+ }
+ }
+
+ // MARK: - Realm methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func loadBlockers() {
+
+ blockerIds.removeAll()
+
+ let predicate = NSPredicate(format: "isDeleted == NO")
+ let dbblockers = DBBlocker.objects(with: predicate)
+
+ for i in 0.. Int {
+
+ return sections.count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return sections[section].count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+
+ return (sections[section].count != 0) ? collation.sectionTitles[section] : nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func sectionIndexTitles(for tableView: UITableView) -> [String]? {
+
+ return collation.sectionIndexTitles
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
+
+ return collation.section(forSectionIndexTitle: index)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "PeopleCell", for: indexPath) as! PeopleCell
+
+ let dbuser = sections[indexPath.section][indexPath.row]
+ cell.bindData(dbuser: dbuser)
+ cell.loadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+
+ return cell
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let dbuser = sections[indexPath.section][indexPath.row]
+
+ let profileView = ProfileView()
+ profileView.myInit(userId: dbuser.objectId, chat: true)
+ profileView.hidesBottomBarWhenPushed = true
+ navigationController?.pushViewController(profileView, animated: true)
+ }
+
+ // MARK: - UISearchBarDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+
+ loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidBeginEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(true, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidEndEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(false, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarCancelButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.text = ""
+ searchBar.resignFirstResponder()
+ loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarSearchButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.resignFirstResponder()
+ }
+}
diff --git a/Messenger/Classes/Tabs/03_People/PeopleView.xib b/Messenger/Classes/Tabs/03_People/PeopleView.xib
new file mode 100644
index 00000000..92c47d02
--- /dev/null
+++ b/Messenger/Classes/Tabs/03_People/PeopleView.xib
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/04_Groups/01_CreateGroup/CreateGroupView.swift b/Messenger/Classes/Tabs/04_Groups/01_CreateGroup/CreateGroupView.swift
new file mode 100644
index 00000000..4130f285
--- /dev/null
+++ b/Messenger/Classes/Tabs/04_Groups/01_CreateGroup/CreateGroupView.swift
@@ -0,0 +1,11 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
diff --git a/Messenger/Classes/Tabs/04_Groups/01_CreateGroup/CreateGroupView.xib b/Messenger/Classes/Tabs/04_Groups/01_CreateGroup/CreateGroupView.xib
new file mode 100755
index 00000000..a0bfaac9
--- /dev/null
+++ b/Messenger/Classes/Tabs/04_Groups/01_CreateGroup/CreateGroupView.xib
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/04_Groups/GroupsCell.swift b/Messenger/Classes/Tabs/04_Groups/GroupsCell.swift
new file mode 100644
index 00000000..005c9373
--- /dev/null
+++ b/Messenger/Classes/Tabs/04_Groups/GroupsCell.swift
@@ -0,0 +1,54 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class GroupsCell: UITableViewCell {
+
+ @IBOutlet var imageGroup: UIImageView!
+ @IBOutlet var labelName: UILabel!
+ @IBOutlet var labelMembers: UILabel!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(dbgroup: DBGroup) {
+
+ labelName.text = dbgroup.name
+
+ let members = dbgroup.members.components(separatedBy: ",")
+ labelMembers.text = "\(members.count) members"
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadImage(dbgroup: DBGroup, tableView: UITableView, indexPath: IndexPath) {
+
+ imageGroup.layer.cornerRadius = imageGroup.frame.size.width / 2
+ imageGroup.layer.masksToBounds = true
+
+ if let path = DownloadManager.pathImage(link: dbgroup.picture) {
+ imageGroup.image = UIImage(contentsOfFile: path)
+ } else {
+ imageGroup.image = UIImage(named: "groups_blank")
+ downloadImage(dbgroup: dbgroup, tableView: tableView, indexPath: indexPath)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func downloadImage(dbgroup: DBGroup, tableView: UITableView, indexPath: IndexPath) {
+
+ DownloadManager.image(link: dbgroup.picture) { path, error, network in
+ if (error == nil) {
+ if (tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false) {
+ let cell = tableView.cellForRow(at: indexPath) as! GroupsCell
+ cell.imageGroup.image = UIImage(contentsOfFile: path!)
+ }
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Tabs/04_Groups/GroupsCell.xib b/Messenger/Classes/Tabs/04_Groups/GroupsCell.xib
new file mode 100755
index 00000000..c6fb057d
--- /dev/null
+++ b/Messenger/Classes/Tabs/04_Groups/GroupsCell.xib
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/04_Groups/GroupsView.swift b/Messenger/Classes/Tabs/04_Groups/GroupsView.swift
new file mode 100644
index 00000000..91cf6b97
--- /dev/null
+++ b/Messenger/Classes/Tabs/04_Groups/GroupsView.swift
@@ -0,0 +1,192 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class GroupsView: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var searchBar: UISearchBar!
+ @IBOutlet var tableView: UITableView!
+
+ private var dbgroups: RLMResults = DBGroup.objects(with: NSPredicate(value: false))
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
+
+ super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
+
+ tabBarItem.image = UIImage(named: "tab_groups")
+ tabBarItem.title = "Groups"
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(loadGroups), name: NOTIFICATION_USER_LOGGED_IN)
+ NotificationCenterX.addObserver(target: self, selector: #selector(actionCleanup), name: NOTIFICATION_USER_LOGGED_OUT)
+ NotificationCenterX.addObserver(target: self, selector: #selector(refreshTableView), name: NOTIFICATION_REFRESH_GROUPS)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ required init?(coder aDecoder: NSCoder) {
+
+ super.init(coder: aDecoder)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Groups"
+
+ navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(actionNew))
+
+ tableView.register(UINib(nibName: "GroupsCell", bundle: nil), forCellReuseIdentifier: "GroupsCell")
+
+ tableView.tableFooterView = UIView()
+
+ if (FUser.currentId() != "") {
+ loadGroups()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidAppear(_ animated: Bool) {
+
+ super.viewDidAppear(animated)
+
+ if (FUser.currentId() != "") {
+ if (FUser.isOnboardOk()) {
+ AdvertCustom(target: self);
+ } else {
+ OnboardUser(target: self)
+ }
+ } else {
+ LoginUser(target: self)
+ }
+ }
+
+ // MARK: - Realm methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func loadGroups() {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func deleteGroup(dbgroup: DBGroup) {
+
+ }
+
+ // MARK: - Refresh methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func refreshTableView() {
+
+ tableView.reloadData()
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionNewGroup() {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionNew() {
+
+ }
+
+ // MARK: - Cleanup methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCleanup() {
+
+ refreshTableView()
+ }
+
+ // MARK: - UIScrollViewDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+
+ view.endEditing(true)
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return Int(dbgroups.count)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "GroupsCell", for: indexPath) as! GroupsCell
+
+ let dbgroup = dbgroups[UInt(indexPath.row)] as! DBGroup
+
+ cell.bindData(dbgroup: dbgroup)
+ cell.loadImage(dbgroup: dbgroup, tableView: tableView, indexPath: indexPath)
+
+ return cell
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
+
+ let dbgroup = dbgroups[UInt(indexPath.row)] as! DBGroup
+ return (dbgroup.userId == FUser.currentId())
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
+
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ }
+
+ // MARK: - UISearchBarDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+
+ loadGroups()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidBeginEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(true, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidEndEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(false, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarCancelButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.text = ""
+ searchBar.resignFirstResponder()
+ loadGroups()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarSearchButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.resignFirstResponder()
+ }
+}
diff --git a/Messenger/Classes/Tabs/04_Groups/GroupsView.xib b/Messenger/Classes/Tabs/04_Groups/GroupsView.xib
new file mode 100755
index 00000000..e729a5cd
--- /dev/null
+++ b/Messenger/Classes/Tabs/04_Groups/GroupsView.xib
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/01_EditProfile/EditProfileView.swift b/Messenger/Classes/Tabs/05_Settings/01_EditProfile/EditProfileView.swift
new file mode 100644
index 00000000..5d352290
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/01_EditProfile/EditProfileView.swift
@@ -0,0 +1,320 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class EditProfileView: UIViewController, UITableViewDataSource, UITableViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate, CountriesDelegate {
+
+ @IBOutlet var tableView: UITableView!
+ @IBOutlet var viewHeader: UIView!
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var cellFirstname: UITableViewCell!
+ @IBOutlet var cellLastname: UITableViewCell!
+ @IBOutlet var cellCountry: UITableViewCell!
+ @IBOutlet var cellLocation: UITableViewCell!
+ @IBOutlet var cellPhone: UITableViewCell!
+ @IBOutlet var fieldFirstname: UITextField!
+ @IBOutlet var fieldLastname: UITextField!
+ @IBOutlet var labelPlaceholder: UILabel!
+ @IBOutlet var labelCountry: UILabel!
+ @IBOutlet var fieldLocation: UITextField!
+ @IBOutlet var fieldPhone: UITextField!
+
+ private var isOnboard = false
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func myInit(isOnboard isOnboard_: Bool) {
+
+ isOnboard = isOnboard_
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Edit Profile"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(actionDone))
+
+ let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
+ tableView.addGestureRecognizer(gestureRecognizer)
+ gestureRecognizer.cancelsTouchesInView = false
+
+ tableView.tableHeaderView = viewHeader
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ loadUser()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+
+ dismissKeyboard()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func dismissKeyboard() {
+
+ view.endEditing(true)
+ }
+
+ // MARK: - Backend actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadUser() {
+
+ let user = FUser.currentUser()
+
+ labelInitials.text = user.initials()
+ if let picture = user[FUSER_PICTURE] as? String {
+ DownloadManager.image(link: picture) { path, error, network in
+ if (error == nil) {
+ self.imageUser.image = UIImage(contentsOfFile: path!)
+ self.labelInitials.text = nil
+ }
+ }
+ }
+
+ fieldFirstname.text = user[FUSER_FIRSTNAME] as? String
+ fieldLastname.text = user[FUSER_LASTNAME] as? String
+
+ labelCountry.text = user[FUSER_COUNTRY] as? String
+ fieldLocation.text = user[FUSER_LOCATION] as? String
+
+ fieldPhone.text = user[FUSER_PHONE] as? String
+
+ let loginMethod = user[FUSER_LOGINMETHOD] as? String
+ fieldPhone.isUserInteractionEnabled = (loginMethod != LOGIN_PHONE)
+
+ updateDetails()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveUser(firstname: String, lastname: String, country: String, location: String, phone: String) {
+
+ let user = FUser.currentUser()
+
+ user[FUSER_FIRSTNAME] = firstname
+ user[FUSER_LASTNAME] = lastname
+ user[FUSER_FULLNAME] = "\(firstname) \(lastname)"
+ user[FUSER_COUNTRY] = country
+ user[FUSER_LOCATION] = location
+ user[FUSER_PHONE] = phone
+
+ user.saveInBackground(block: { error in
+ if (error == nil) {
+ Account.update()
+ } else {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveUserPicture(link: String) {
+
+ let user = FUser.currentUser()
+
+ user[FUSER_PICTURE] = link
+
+ user.saveInBackground(block: { error in
+ if (error == nil) {
+ Account.update()
+ } else {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveUserThumbnail(link: String) {
+
+ let user = FUser.currentUser()
+
+ user[FUSER_THUMBNAIL] = link
+
+ user.saveInBackground(block: { error in
+ if (error != nil) {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCancel() {
+
+ if (isOnboard) {
+ LogoutUser(delAccount: DEL_ACCOUNT_ALL)
+ }
+ dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionDone() {
+
+ let firstname = fieldFirstname.text ?? ""
+ let lastname = fieldLastname.text ?? ""
+ let country = labelCountry.text ?? ""
+ let location = fieldLocation.text ?? ""
+ let phone = fieldPhone.text ?? ""
+
+ if (firstname.count == 0) { ProgressHUD.showError("Firstname must be set."); return }
+ if (lastname.count == 0) { ProgressHUD.showError("Lastname must be set."); return }
+ if (country.count == 0) { ProgressHUD.showError("Country must be set."); return }
+ if (location.count == 0) { ProgressHUD.showError("Location must be set."); return }
+ if (phone.count == 0) { ProgressHUD.showError("Phone number must be set."); return }
+
+ saveUser(firstname: firstname, lastname: lastname, country: country, location: location, phone: phone)
+
+ dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionPhoto(_ sender: Any) {
+
+ let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+ alert.addAction(UIAlertAction(title: "Open Camera", style: .default, handler: { action in
+ PresentPhotoCamera(target: self, edit: true)
+ }))
+ alert.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: { action in
+ PresentPhotoLibrary(target: self, edit: true)
+ }))
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+
+ present(alert, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionCountries() {
+
+ let countriesView = CountriesView()
+ countriesView.delegate = self
+ let navController = NavigationController(rootViewController: countriesView)
+ present(navController, animated: true)
+ }
+
+ // MARK: - UIImagePickerControllerDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
+
+ if let image = info[.editedImage] as? UIImage {
+ uploadUserPicture(image: image)
+ uploadUserThumbnail(image: image)
+ }
+
+ picker.dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func uploadUserPicture(image: UIImage) {
+
+ let squared = Image.square(image: image, size: 300)
+ if let data = image.jpegData(compressionQuality: 0.6) {
+ UploadManager.upload(data: data, name: "profile_picture", ext: "jpg", completion: { link, error in
+ if (error == nil) {
+ self.labelInitials.text = nil
+ self.imageUser.image = squared
+ DownloadManager.saveImage(data: data, link: link!)
+ self.saveUserPicture(link: link!)
+ } else {
+ ProgressHUD.showError("Picture upload error.")
+ }
+ })
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func uploadUserThumbnail(image: UIImage) {
+
+ let squared = Image.square(image: image, size: 100)
+ if let data = squared.jpegData(compressionQuality: 0.6) {
+ UploadManager.upload(data: data, name: "profile_thumbnail", ext: "jpg", completion: { link, error in
+ if (error == nil) {
+ DownloadManager.saveImage(data: data, link: link!)
+ self.saveUserThumbnail(link: link!)
+ } else {
+ ProgressHUD.showError("Thumbnail upload error.")
+ }
+ })
+ }
+ }
+
+ // MARK: - CountriesDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func didSelectCountry(name: String, code: String) {
+
+ labelCountry.text = name
+ fieldLocation.becomeFirstResponder()
+ updateDetails()
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 2
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ if (section == 0) { return 4 }
+ if (section == 1) { return 1 }
+
+ return 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { return cellFirstname }
+ if (indexPath.section == 0) && (indexPath.row == 1) { return cellLastname }
+ if (indexPath.section == 0) && (indexPath.row == 2) { return cellCountry }
+ if (indexPath.section == 0) && (indexPath.row == 3) { return cellLocation }
+ if (indexPath.section == 1) && (indexPath.row == 0) { return cellPhone }
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ if (indexPath.section == 0) && (indexPath.row == 2) { actionCountries() }
+ }
+
+ // MARK: - UITextField delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+
+ if (textField == fieldFirstname) { fieldLastname.becomeFirstResponder() }
+ if (textField == fieldLastname) { actionCountries() }
+ if (textField == fieldLocation) { fieldPhone.becomeFirstResponder() }
+ if (textField == fieldPhone) { actionDone() }
+
+ return true
+ }
+
+ // MARK: - Helper methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateDetails() {
+
+ labelPlaceholder.isHidden = labelCountry.text != nil
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/01_EditProfile/EditProfileView.xib b/Messenger/Classes/Tabs/05_Settings/01_EditProfile/EditProfileView.xib
new file mode 100644
index 00000000..afc689ce
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/01_EditProfile/EditProfileView.xib
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/02_Password/PasswordView.swift b/Messenger/Classes/Tabs/05_Settings/02_Password/PasswordView.swift
new file mode 100644
index 00000000..37c31b45
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/02_Password/PasswordView.swift
@@ -0,0 +1,160 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class PasswordView: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
+
+ @IBOutlet var tableView: UITableView!
+ @IBOutlet var cellPassword0: UITableViewCell!
+ @IBOutlet var cellPassword1: UITableViewCell!
+ @IBOutlet var cellPassword2: UITableViewCell!
+ @IBOutlet var fieldPassword0: UITextField!
+ @IBOutlet var fieldPassword1: UITextField!
+ @IBOutlet var fieldPassword2: UITextField!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Change Password"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(actionDone))
+
+ let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
+ tableView.addGestureRecognizer(gestureRecognizer)
+ gestureRecognizer.cancelsTouchesInView = false
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidAppear(_ animated: Bool) {
+
+ super.viewDidAppear(animated)
+ fieldPassword0.becomeFirstResponder()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+ dismissKeyboard()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func dismissKeyboard() {
+
+ view.endEditing(true)
+ }
+
+ // MARK: - Backend actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func checkPassword() {
+
+ ProgressHUD.show(nil, interaction: false)
+
+ if let firuser = Auth.auth().currentUser {
+ if let email = firuser.email {
+ if let password = fieldPassword0.text {
+ let credential = EmailAuthProvider.credential(withEmail: email, password: password)
+ firuser.reauthenticateAndRetrieveData(with: credential) { authResult, error in
+ if (error == nil) {
+ self.updatePassword()
+ } else {
+ ProgressHUD.showError(error?.localizedDescription)
+ }
+ }
+ } else { ProgressHUD.showError("Check password error.") }
+ } else { ProgressHUD.showError("Check password error.") }
+ } else { ProgressHUD.showError("Check password error.") }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updatePassword() {
+
+ if let firuser = Auth.auth().currentUser {
+ if let password = fieldPassword1.text {
+ firuser.updatePassword(to: password) { error in
+ if (error == nil) {
+ ProgressHUD.showSuccess("Password changed.")
+ self.dismiss(animated: true)
+ } else {
+ ProgressHUD.showError(error?.localizedDescription)
+ }
+ }
+ } else { ProgressHUD.showError("Update password error.") }
+ } else { ProgressHUD.showError("Update password error.") }
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCancel() {
+
+ dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionDone() {
+
+ let password0 = fieldPassword0.text ?? ""
+ let password1 = fieldPassword1.text ?? ""
+ let password2 = fieldPassword2.text ?? ""
+
+ if (password0.count == 0) { ProgressHUD.showError("Current Password must be set."); return }
+ if (password1.count == 0) { ProgressHUD.showError("New Password must be set."); return }
+ if (password1 != password2) { ProgressHUD.showError("New Passwords must be the same."); return }
+
+ checkPassword()
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 2
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ if (section == 0) { return 1 }
+ if (section == 1) { return 2 }
+
+ return 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { return cellPassword0 }
+ if (indexPath.section == 1) && (indexPath.row == 0) { return cellPassword1 }
+ if (indexPath.section == 1) && (indexPath.row == 1) { return cellPassword2 }
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+ }
+
+ // MARK: - UITextField delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+
+ if (textField == fieldPassword0) { fieldPassword1.becomeFirstResponder() }
+ if (textField == fieldPassword1) { fieldPassword2.becomeFirstResponder() }
+ if (textField == fieldPassword2) { actionDone() }
+
+ return true
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/02_Password/PasswordView.xib b/Messenger/Classes/Tabs/05_Settings/02_Password/PasswordView.xib
new file mode 100644
index 00000000..1b586461
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/02_Password/PasswordView.xib
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/03_Status/01_CustomStatus/CustomStatusView.swift b/Messenger/Classes/Tabs/05_Settings/03_Status/01_CustomStatus/CustomStatusView.swift
new file mode 100644
index 00000000..75392354
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/03_Status/01_CustomStatus/CustomStatusView.swift
@@ -0,0 +1,77 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class CustomStatusView: UIViewController {
+
+ @IBOutlet var fieldStatus: UITextField!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Custom status"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(actionSave))
+
+ loadUser()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidAppear(_ animated: Bool) {
+
+ super.viewDidAppear(animated)
+ fieldStatus.becomeFirstResponder()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+ view.endEditing(true)
+ }
+
+ // MARK: - Backend actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadUser() {
+
+ fieldStatus.text = FUser.status()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveUser() {
+
+ let user = FUser.currentUser()
+
+ user[FUSER_STATUS] = fieldStatus.text
+
+ user.saveInBackground(block: { error in
+ if (error != nil) {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCancel() {
+
+ dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionSave() {
+
+ saveUser()
+ dismiss(animated: true)
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/03_Status/01_CustomStatus/CustomStatusView.xib b/Messenger/Classes/Tabs/05_Settings/03_Status/01_CustomStatus/CustomStatusView.xib
new file mode 100644
index 00000000..120115f7
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/03_Status/01_CustomStatus/CustomStatusView.xib
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/03_Status/StatusView.swift b/Messenger/Classes/Tabs/05_Settings/03_Status/StatusView.swift
new file mode 100644
index 00000000..88626119
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/03_Status/StatusView.swift
@@ -0,0 +1,146 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class StatusView: UIViewController, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var tableView: UITableView!
+ @IBOutlet var cellStatus: UITableViewCell!
+ @IBOutlet var cellClear: UITableViewCell!
+
+ private var dbuserstatuses: RLMResults = DBUserStatus.objects(with: NSPredicate(value: false))
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Status"
+
+ loadStatuses()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ loadUser()
+ }
+
+ // MARK: - Backend actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadStatuses() {
+
+ dbuserstatuses = DBUserStatus.allObjects().sortedResults(usingKeyPath: FUSERSTATUS_CREATEDAT, ascending: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadUser() {
+
+ cellStatus.textLabel?.text = FUser.status()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveUser(status: String) {
+
+ let user = FUser.currentUser()
+
+ user[FUSER_STATUS] = status
+
+ user.saveInBackground(block: { error in
+ if (error != nil) {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return (dbuserstatuses.count == 0) ? 1 : 3
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ if (section == 0) { return 1 }
+ if (section == 1) { return Int(dbuserstatuses.count) }
+ if (section == 2) { return 1 }
+
+ return 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+
+ if (section == 0) { return "Your current status is" }
+ if (section == 1) { return "Select your new status" }
+
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ if (indexPath.section == 0) && (indexPath.row == 0) {
+ return cellStatus
+ }
+
+ if (indexPath.section == 1) {
+ var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell")
+ if (cell == nil) { cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell") }
+
+ let dbuserstatus = dbuserstatuses[UInt(indexPath.row)] as! DBUserStatus
+ cell.textLabel?.text = dbuserstatus.name
+
+ return cell
+ }
+
+ if (indexPath.section == 2) && (indexPath.row == 0) {
+ return cellClear
+ }
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ if (indexPath.section == 0) {
+ let customStatusView = CustomStatusView()
+ let navController = NavigationController(rootViewController: customStatusView)
+ present(navController, animated: true)
+ }
+
+ if (indexPath.section == 1) {
+ let dbuserstatus = dbuserstatuses[UInt(indexPath.row)] as! DBUserStatus
+ saveUser(status: dbuserstatus.name)
+ updateStatus(status: dbuserstatus.name)
+ }
+
+ if (indexPath.section == 2) {
+ saveUser(status: "")
+ updateStatus(status: "")
+ }
+ }
+
+ // MARK: - Helper methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateStatus(status: String) {
+
+ cellStatus.textLabel?.text = status
+ tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .none)
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/03_Status/StatusView.xib b/Messenger/Classes/Tabs/05_Settings/03_Status/StatusView.xib
new file mode 100644
index 00000000..b44a41f9
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/03_Status/StatusView.xib
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedCell.swift b/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedCell.swift
new file mode 100644
index 00000000..d6032175
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedCell.swift
@@ -0,0 +1,63 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class BlockedCell: UITableViewCell {
+
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var labelName: UILabel!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(dbuser: DBUser) {
+
+ labelName.text = dbuser.fullname
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadImage(dbuser: DBUser, tableView: UITableView, indexPath: IndexPath) {
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ if (dbuser.thumbnail != "") {
+ if let path = DownloadManager.pathImage(link: dbuser.thumbnail) {
+ imageUser.image = UIImage(contentsOfFile: path)
+ labelInitials.text = nil
+ } else {
+ imageUser.image = UIImage(named: "blocked_blank")
+ labelInitials.text = dbuser.initials()
+ downloadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+ }
+ } else {
+ imageUser.image = UIImage(named: "blocked_blank")
+ labelInitials.text = dbuser.initials()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func downloadImage(dbuser: DBUser, tableView: UITableView, indexPath: IndexPath) {
+
+ DownloadManager.image(link: dbuser.thumbnail) { path, error, network in
+ if (error == nil) {
+ if (tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false) {
+ let cell = tableView.cellForRow(at: indexPath) as! BlockedCell
+ cell.imageUser.image = UIImage(contentsOfFile: path!)
+ cell.labelInitials.text = nil
+ }
+ } else if ((error! as NSError).code == 102) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ self.downloadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+ }
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedCell.xib b/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedCell.xib
new file mode 100644
index 00000000..4212dc1c
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedCell.xib
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedView.swift b/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedView.swift
new file mode 100644
index 00000000..dc275fb5
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedView.swift
@@ -0,0 +1,159 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class BlockedView: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var searchBar: UISearchBar!
+ @IBOutlet var tableView: UITableView!
+
+ private var blockedIds: [String] = []
+ private var dbusers: RLMResults = DBUser.objects(with: NSPredicate(value: false))
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Blocked Users"
+
+ navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
+
+ tableView.register(UINib(nibName: "BlockedCell", bundle: nil), forCellReuseIdentifier: "BlockedCell")
+
+ tableView.tableFooterView = UIView()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ loadBlockeds()
+ }
+
+ // MARK: - Realm methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadBlockeds() {
+
+ blockedIds.removeAll()
+
+ let predicate = NSPredicate(format: "isDeleted == NO")
+ let dbblockeds = DBBlocked.objects(with: predicate)
+
+ for i in 0.. Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return Int(dbusers.count)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "BlockedCell", for: indexPath) as! BlockedCell
+
+ let dbuser = dbusers[UInt(indexPath.row)] as! DBUser
+ cell.bindData(dbuser: dbuser)
+ cell.loadImage(dbuser: dbuser, tableView: tableView, indexPath: indexPath)
+
+ return cell
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let dbuser = dbusers[UInt(indexPath.row)] as! DBUser
+
+ let profileView = ProfileView()
+ profileView.myInit(userId: dbuser.objectId, chat: true)
+ navigationController?.pushViewController(profileView, animated: true)
+ }
+
+ // MARK: - UISearchBarDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+
+ loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidBeginEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(true, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidEndEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(false, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarCancelButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.text = ""
+ searchBar.resignFirstResponder()
+ loadUsers()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarSearchButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.resignFirstResponder()
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedView.xib b/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedView.xib
new file mode 100644
index 00000000..6ada77e8
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/04_Blocked/BlockedView.xib
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveCell.swift b/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveCell.swift
new file mode 100644
index 00000000..c942bcfe
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveCell.swift
@@ -0,0 +1,71 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class ArchiveCell: MGSwipeTableCell {
+
+ @IBOutlet var viewUnread: UIView!
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var labelDescription: UILabel!
+ @IBOutlet var labelLastMessage: UILabel!
+ @IBOutlet var labelElapsed: UILabel!
+ @IBOutlet var imageMuted: UIImageView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(dbchat: DBChat) {
+
+ let lastRead = Status.lastRead(chatId: dbchat.chatId)
+ let mutedUntil = Status.mutedUntil(chatId: dbchat.chatId)
+
+ viewUnread.isHidden = (lastRead >= dbchat.lastIncoming)
+
+ labelDescription.text = dbchat.details
+ labelLastMessage.text = dbchat.lastMessage
+
+ labelElapsed.text = TimeElapsed(timestamp: dbchat.lastMessageDate)
+ imageMuted.isHidden = (mutedUntil < Date().timestamp())
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadImage(dbchat: DBChat, tableView: UITableView, indexPath: IndexPath) {
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ if let path = DownloadManager.pathImage(link: dbchat.picture) {
+ imageUser.image = UIImage(contentsOfFile: path)
+ labelInitials.text = nil
+ } else {
+ imageUser.image = UIImage(named: "archive_blank")
+ labelInitials.text = dbchat.initials
+ downloadImage(dbchat: dbchat, tableView: tableView, indexPath: indexPath)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func downloadImage(dbchat: DBChat, tableView: UITableView, indexPath: IndexPath) {
+
+ DownloadManager.image(link: dbchat.picture) { path, error, network in
+ if (error == nil) {
+ if (tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false) {
+ let cell = tableView.cellForRow(at: indexPath) as! ArchiveCell
+ cell.imageUser.image = UIImage(contentsOfFile: path!)
+ cell.labelInitials.text = nil
+ }
+ } else if ((error! as NSError).code == 102) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ self.downloadImage(dbchat: dbchat, tableView: tableView, indexPath: indexPath)
+ }
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveCell.xib b/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveCell.xib
new file mode 100644
index 00000000..8a836c67
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveCell.xib
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveView.swift b/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveView.swift
new file mode 100644
index 00000000..5604a8b8
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveView.swift
@@ -0,0 +1,227 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class ArchiveView: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate, MGSwipeTableCellDelegate {
+
+ @IBOutlet var searchBar: UISearchBar!
+ @IBOutlet var tableView: UITableView!
+
+ private var timer: Timer?
+ private var dbchats: RLMResults = DBChat.objects(with: NSPredicate(value: false))
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Archived Chats"
+
+ tableView.register(UINib(nibName: "ArchiveCell", bundle: nil), forCellReuseIdentifier: "ArchiveCell")
+
+ tableView.tableFooterView = UIView()
+
+ loadChats()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ timer = Timer.scheduledTimer(timeInterval: 30.0, target: self, selector: #selector(refreshTableView), userInfo: nil, repeats: true)
+
+ refreshTableView()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+
+ timer?.invalidate()
+ timer = nil
+ }
+
+ // MARK: - Realm methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadChats() {
+
+ var predicate = NSPredicate(format: "isArchived == YES AND isDeleted == NO")
+
+ if let text = searchBar.text {
+ if (text.count != 0) {
+ predicate = NSPredicate(format: "isArchived == YES AND isDeleted == NO AND details CONTAINS[c] %@", text)
+ }
+ }
+
+ dbchats = DBChat.objects(with: predicate).sortedResults(usingKeyPath: "lastMessageDate", ascending: false)
+
+ refreshTableView()
+ }
+
+ // MARK: - Refresh methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func refreshTableView() {
+
+ tableView.reloadData()
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionChatGroup(groupId: String) {
+
+ AdvertCustom(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionChatPrivate(recipientId: String) {
+
+ let chatPrivateView = ChatPrivateView()
+ chatPrivateView.myInit(recipientId: recipientId)
+ navigationController?.pushViewController(chatPrivateView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionMore(index: Int) {
+
+ let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+ alert.addAction(UIAlertAction(title: "Unarchive", style: .default, handler: { action in
+ self.actionUnarchive(index: index)
+ }))
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+
+ present(alert, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionUnarchive(index: Int) {
+
+ let dbchat = dbchats[UInt(index)] as! DBChat
+ Chat.unarchiveItem(dbchat: dbchat)
+
+ let indexPath = IndexPath(row: index, section: 0)
+ tableView.deleteRows(at: [indexPath], with: .fade)
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
+ self.refreshTableView()
+ }
+
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_CHATS)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionDelete(index: Int) {
+
+ let dbchat = dbchats[UInt(index)] as! DBChat
+ Chat.deleteItem(dbchat: dbchat)
+
+ let indexPath = IndexPath(row: index, section: 0)
+ tableView.deleteRows(at: [indexPath], with: .fade)
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
+ self.refreshTableView()
+ }
+ }
+
+ // MARK: - UIScrollViewDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+
+ view.endEditing(true)
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return Int(dbchats.count)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: "ArchiveCell", for: indexPath) as! ArchiveCell
+
+ cell.rightButtons = [MGSwipeButton(title: "Delete", backgroundColor: UIColor.red),
+ MGSwipeButton(title: "More", backgroundColor: UIColor.lightGray)]
+
+ cell.delegate = self
+ cell.tag = indexPath.row
+
+ let dbchat = dbchats[UInt(indexPath.row)] as! DBChat
+ cell.bindData(dbchat: dbchat)
+ cell.loadImage(dbchat: dbchat, tableView: tableView, indexPath: indexPath)
+
+ return cell
+ }
+
+ // MARK: - MGSwipeTableCellDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func swipeTableCell(_ cell: MGSwipeTableCell, tappedButtonAt index: Int, direction: MGSwipeDirection, fromExpansion: Bool) -> Bool {
+
+ if (index == 0) { actionDelete(index: cell.tag) }
+ if (index == 1) { actionMore(index: cell.tag) }
+
+ return true
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let dbchat = dbchats[UInt(indexPath.row)] as! DBChat
+
+ if (dbchat.groupId.count != 0) { actionChatGroup(groupId: dbchat.groupId) }
+ if (dbchat.recipientId.count != 0) { actionChatPrivate(recipientId: dbchat.recipientId) }
+ }
+
+ // MARK: - UISearchBarDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+
+ loadChats()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidBeginEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(true, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarTextDidEndEditing(_ searchBar_: UISearchBar) {
+
+ searchBar.setShowsCancelButton(false, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarCancelButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.text = ""
+ searchBar.resignFirstResponder()
+ loadChats()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func searchBarSearchButtonClicked(_ searchBar_: UISearchBar) {
+
+ searchBar.resignFirstResponder()
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveView.xib b/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveView.xib
new file mode 100644
index 00000000..9437ae04
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/05_Archive/ArchiveView.xib
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/06_Cache/01_KeepMedia/KeepMediaView.swift b/Messenger/Classes/Tabs/05_Settings/06_Cache/01_KeepMedia/KeepMediaView.swift
new file mode 100644
index 00000000..e0b6b7c9
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/06_Cache/01_KeepMedia/KeepMediaView.swift
@@ -0,0 +1,99 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class KeepMediaView: UIViewController, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var tableView: UITableView!
+ @IBOutlet var cellWeek: UITableViewCell!
+ @IBOutlet var cellMonth: UITableViewCell!
+ @IBOutlet var cellForever: UITableViewCell!
+
+ private var keepMedia: Int = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Keep Media"
+
+ loadUser()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadUser() {
+
+ keepMedia = FUser.keepMedia()
+ updateDetails()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveUser() {
+
+ let user = FUser.currentUser()
+
+ user[FUSER_KEEPMEDIA] = keepMedia
+
+ user.saveInBackground(block: { error in
+ if (error != nil) {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return 3
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { return cellWeek }
+ if (indexPath.section == 0) && (indexPath.row == 1) { return cellMonth }
+ if (indexPath.section == 0) && (indexPath.row == 2) { return cellForever }
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { keepMedia = Int(KEEPMEDIA_WEEK) }
+ if (indexPath.section == 0) && (indexPath.row == 1) { keepMedia = Int(KEEPMEDIA_MONTH) }
+ if (indexPath.section == 0) && (indexPath.row == 2) { keepMedia = Int(KEEPMEDIA_FOREVER) }
+
+ updateDetails()
+ saveUser()
+ }
+
+ // MARK: - Helper methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateDetails() {
+
+ cellWeek.accessoryType = (keepMedia == KEEPMEDIA_WEEK) ? .checkmark : .none
+ cellMonth.accessoryType = (keepMedia == KEEPMEDIA_MONTH) ? .checkmark : .none
+ cellForever.accessoryType = (keepMedia == KEEPMEDIA_FOREVER) ? .checkmark : .none
+
+ tableView.reloadData()
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/06_Cache/01_KeepMedia/KeepMediaView.xib b/Messenger/Classes/Tabs/05_Settings/06_Cache/01_KeepMedia/KeepMediaView.xib
new file mode 100644
index 00000000..16dcae4a
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/06_Cache/01_KeepMedia/KeepMediaView.xib
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/06_Cache/CacheView.swift b/Messenger/Classes/Tabs/05_Settings/06_Cache/CacheView.swift
new file mode 100644
index 00000000..f7855654
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/06_Cache/CacheView.swift
@@ -0,0 +1,133 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class CacheView: UIViewController, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var tableView: UITableView!
+ @IBOutlet var cellKeepMedia: UITableViewCell!
+ @IBOutlet var cellDescription: UITableViewCell!
+ @IBOutlet var cellClearCache: UITableViewCell!
+ @IBOutlet var cellCacheSize: UITableViewCell!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Cache Settings"
+
+ navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
+
+ updateDetails()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ loadUser()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidAppear(_ animated: Bool) {
+
+ super.viewDidAppear(animated)
+
+ AdvertPremium(target: self);
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadUser() {
+
+ let keepMedia = FUser.keepMedia()
+
+ if (keepMedia == KEEPMEDIA_WEEK) { cellKeepMedia.detailTextLabel?.text = "1 week" }
+ if (keepMedia == KEEPMEDIA_MONTH) { cellKeepMedia.detailTextLabel?.text = "1 month" }
+ if (keepMedia == KEEPMEDIA_FOREVER) { cellKeepMedia.detailTextLabel?.text = "Forever" }
+
+ tableView.reloadData()
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionKeepMedia() {
+
+ let keepMediaView = KeepMediaView()
+ navigationController?.pushViewController(keepMediaView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionClearCache() {
+
+ CacheManager.cleanupManual(logout: false)
+ updateDetails()
+ }
+
+ // MARK: - Helper methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateDetails() {
+
+ let total = CacheManager.total()
+
+ if (Int(total) < 1000 * 1024) {
+ cellCacheSize.textLabel?.text = "Cache size: \(Int(total) / 1024) Kbytes"
+ } else {
+ cellCacheSize.textLabel?.text = "Cache size: \(Int(total) / (1000 * 1024)) Mbytes"
+ }
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 2
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ if (section == 0) { return 2 }
+ if (section == 1) { return 2 }
+
+ return 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+
+ if (indexPath.section == 0) && (indexPath.row == 1) { return 160 }
+
+ return 50
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { return cellKeepMedia }
+ if (indexPath.section == 0) && (indexPath.row == 1) { return cellDescription }
+ if (indexPath.section == 1) && (indexPath.row == 0) { return cellClearCache }
+ if (indexPath.section == 1) && (indexPath.row == 1) { return cellCacheSize }
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { actionKeepMedia() }
+ if (indexPath.section == 1) && (indexPath.row == 0) { actionClearCache() }
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/06_Cache/CacheView.xib b/Messenger/Classes/Tabs/05_Settings/06_Cache/CacheView.xib
new file mode 100644
index 00000000..bce23a11
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/06_Cache/CacheView.xib
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/07_Media/01_Network/NetworkView.swift b/Messenger/Classes/Tabs/05_Settings/07_Media/01_Network/NetworkView.swift
new file mode 100644
index 00000000..24e75c98
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/07_Media/01_Network/NetworkView.swift
@@ -0,0 +1,122 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class NetworkView: UIViewController, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var tableView: UITableView!
+ @IBOutlet var cellManual: UITableViewCell!
+ @IBOutlet var cellWiFi: UITableViewCell!
+ @IBOutlet var cellAll: UITableViewCell!
+
+ private var mediaType: Int = 0
+ private var selectedNetwork: Int = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func myInit(mediaType mediaType_: Int) {
+
+ mediaType = mediaType_
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+
+ if mediaType == MEDIA_IMAGE { title = "Image" }
+ if mediaType == MEDIA_VIDEO { title = "Video" }
+ if mediaType == MEDIA_AUDIO { title = "Audio" }
+
+ loadUser()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+
+ saveUser()
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadUser() {
+
+ if (mediaType == MEDIA_IMAGE) { selectedNetwork = FUser.networkImage() }
+ if (mediaType == MEDIA_VIDEO) { selectedNetwork = FUser.networkVideo() }
+ if (mediaType == MEDIA_AUDIO) { selectedNetwork = FUser.networkAudio() }
+
+ updateDetails()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveUser() {
+
+ let user = FUser.currentUser()
+
+ if (mediaType == MEDIA_IMAGE) { user[FUSER_NETWORKIMAGE] = selectedNetwork }
+ if (mediaType == MEDIA_VIDEO) { user[FUSER_NETWORKVIDEO] = selectedNetwork }
+ if (mediaType == MEDIA_AUDIO) { user[FUSER_NETWORKAUDIO] = selectedNetwork }
+
+ user.saveInBackground(block: { error in
+ if (error != nil) {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return 3
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { return cellManual }
+ if (indexPath.section == 0) && (indexPath.row == 1) { return cellWiFi }
+ if (indexPath.section == 0) && (indexPath.row == 2) { return cellAll }
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { selectedNetwork = Int(NETWORK_MANUAL) }
+ if (indexPath.section == 0) && (indexPath.row == 1) { selectedNetwork = Int(NETWORK_WIFI) }
+ if (indexPath.section == 0) && (indexPath.row == 2) { selectedNetwork = Int(NETWORK_ALL) }
+
+ updateDetails()
+ }
+
+ // MARK: - Helper methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateDetails() {
+
+ cellManual.accessoryType = (selectedNetwork == NETWORK_MANUAL) ? .checkmark : .none
+ cellWiFi.accessoryType = (selectedNetwork == NETWORK_WIFI) ? .checkmark : .none
+ cellAll.accessoryType = (selectedNetwork == NETWORK_ALL) ? .checkmark : .none
+
+ tableView.reloadData()
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/07_Media/01_Network/NetworkView.xib b/Messenger/Classes/Tabs/05_Settings/07_Media/01_Network/NetworkView.xib
new file mode 100644
index 00000000..9a08961c
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/07_Media/01_Network/NetworkView.xib
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/07_Media/MediaView.swift b/Messenger/Classes/Tabs/05_Settings/07_Media/MediaView.swift
new file mode 100644
index 00000000..f8a19f4d
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/07_Media/MediaView.swift
@@ -0,0 +1,107 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class MediaView: UIViewController, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var tableView: UITableView!
+ @IBOutlet var cellImage: UITableViewCell!
+ @IBOutlet var cellVideo: UITableViewCell!
+ @IBOutlet var cellAudio: UITableViewCell!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Media Settings"
+
+ navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ loadUser()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidAppear(_ animated: Bool) {
+
+ super.viewDidAppear(animated)
+
+ AdvertPremium(target: self);
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadUser() {
+
+ updateCell(selectedNetwork: FUser.networkImage(), cell: cellImage)
+ updateCell(selectedNetwork: FUser.networkVideo(), cell: cellVideo)
+ updateCell(selectedNetwork: FUser.networkAudio(), cell: cellAudio)
+
+ tableView.reloadData()
+ }
+
+ // MARK: - Helper methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateCell(selectedNetwork: Int, cell: UITableViewCell) {
+
+ if (selectedNetwork == NETWORK_MANUAL) { cell.detailTextLabel?.text = "Manual" }
+ if (selectedNetwork == NETWORK_WIFI) { cell.detailTextLabel?.text = "Wi-Fi" }
+ if (selectedNetwork == NETWORK_ALL) { cell.detailTextLabel?.text = "Wi-Fi + Cellular" }
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionNetwork(mediaType: Int) {
+
+ let networkView = NetworkView()
+ networkView.myInit(mediaType: mediaType)
+ navigationController?.pushViewController(networkView, animated: true)
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return 3
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { return cellImage }
+ if (indexPath.section == 0) && (indexPath.row == 1) { return cellVideo }
+ if (indexPath.section == 0) && (indexPath.row == 2) { return cellAudio }
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { actionNetwork(mediaType: Int(MEDIA_IMAGE)) }
+ if (indexPath.section == 0) && (indexPath.row == 1) { actionNetwork(mediaType: Int(MEDIA_VIDEO)) }
+ if (indexPath.section == 0) && (indexPath.row == 2) { actionNetwork(mediaType: Int(MEDIA_AUDIO)) }
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/07_Media/MediaView.xib b/Messenger/Classes/Tabs/05_Settings/07_Media/MediaView.xib
new file mode 100644
index 00000000..c2f840b7
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/07_Media/MediaView.xib
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersCell.swift b/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersCell.swift
new file mode 100644
index 00000000..5e4d517f
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersCell.swift
@@ -0,0 +1,23 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class WallpapersCell: UICollectionViewCell {
+
+ @IBOutlet var imageItem: UIImageView!
+ @IBOutlet var imageSelected: UIImageView!
+
+ func bindData(path: String) {
+
+ imageItem.image = UIImage(named: path)
+ imageSelected.isHidden = (FUser.wallpaper() != path)
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersCell.xib b/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersCell.xib
new file mode 100755
index 00000000..cda9b699
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersCell.xib
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersView.swift b/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersView.swift
new file mode 100644
index 00000000..f6fd6a8b
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersView.swift
@@ -0,0 +1,121 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class WallpapersView: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UICollectionViewDataSource, UICollectionViewDelegate {
+
+ @IBOutlet var collectionView: UICollectionView!
+
+ private var wallpapers: [String] = []
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Wallpapers"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+
+ collectionView.register(UINib(nibName: "WallpapersCell", bundle: nil), forCellWithReuseIdentifier: "WallpapersCell")
+
+ loadWallpapers()
+ }
+
+ // MARK: - Load stickers
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadWallpapers() {
+
+ if let files = try? FileManager.default.contentsOfDirectory(atPath: Dir.application()) {
+ for file in files.sorted() {
+ if (file.contains("wallpapers")) {
+ wallpapers.append(file)
+ }
+ }
+ }
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveUser(path: String) {
+
+ let user = FUser.currentUser()
+
+ user[FUSER_WALLPAPER] = path
+
+ user.saveInBackground(block: { error in
+ if (error != nil) {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCancel() {
+
+ dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionDone() {
+
+ dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionPhoto(_ sender: Any) {
+
+ AdvertPremium(target: self);
+ }
+
+ // MARK: - UIImagePickerControllerDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
+
+ }
+
+ // MARK: - UICollectionViewDataSource
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in collectionView: UICollectionView) -> Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+
+ return wallpapers.count
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+
+ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WallpapersCell", for: indexPath) as! WallpapersCell
+
+ cell.bindData(path: Dir.application(wallpapers[indexPath.item]))
+
+ return cell
+ }
+
+ // MARK: - UICollectionViewDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+
+ collectionView.deselectItem(at: indexPath, animated: true)
+ collectionView.reloadData()
+
+ saveUser(path: Dir.application(wallpapers[indexPath.item]))
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+ self.actionDone()
+ }
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersView.xib b/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersView.xib
new file mode 100755
index 00000000..49dbeac3
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/08_Wallpapers/WallpapersView.xib
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/09_Privacy/PrivacyView.swift b/Messenger/Classes/Tabs/05_Settings/09_Privacy/PrivacyView.swift
new file mode 100644
index 00000000..b634614b
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/09_Privacy/PrivacyView.swift
@@ -0,0 +1,32 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class PrivacyView: UIViewController {
+
+ @IBOutlet var webView: UIWebView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Privacy Policy"
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ let path = Dir.application("privacy.html")
+ webView.loadRequest(URLRequest(url: URL(fileURLWithPath: path)))
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/09_Privacy/PrivacyView.xib b/Messenger/Classes/Tabs/05_Settings/09_Privacy/PrivacyView.xib
new file mode 100755
index 00000000..6ea9bea6
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/09_Privacy/PrivacyView.xib
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/10_Terms/TermsView.swift b/Messenger/Classes/Tabs/05_Settings/10_Terms/TermsView.swift
new file mode 100644
index 00000000..cf820035
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/10_Terms/TermsView.swift
@@ -0,0 +1,32 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class TermsView: UIViewController {
+
+ @IBOutlet var webView: UIWebView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Terms of Service"
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ let path = Dir.application("terms.html")
+ webView.loadRequest(URLRequest(url: URL(fileURLWithPath: path)))
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/10_Terms/TermsView.xib b/Messenger/Classes/Tabs/05_Settings/10_Terms/TermsView.xib
new file mode 100755
index 00000000..96346928
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/10_Terms/TermsView.xib
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/11_AddAccount/AddAccountView.swift b/Messenger/Classes/Tabs/05_Settings/11_AddAccount/AddAccountView.swift
new file mode 100644
index 00000000..9845452b
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/11_AddAccount/AddAccountView.swift
@@ -0,0 +1,63 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class AddAccountView: UIViewController, LoginEmailDelegate, RegisterEmailDelegate {
+
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Add Account"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCancel() {
+
+ dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionLoginEmail(_ sender: Any) {
+
+ let loginEmailView = LoginEmailView()
+ loginEmailView.delegate = self
+ present(loginEmailView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionRegisterEmail(_ sender: Any) {
+
+ let registerEmailView = RegisterEmailView()
+ registerEmailView.delegate = self
+ present(registerEmailView, animated: true)
+ }
+
+ // MARK: - LoginEmailDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func didLoginEmail() {
+
+ dismiss(animated: true) {
+ UserLoggedIn(loginMethod: LOGIN_EMAIL)
+ }
+ }
+
+ // MARK: - RegisterEmailDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func didRegisterUser() {
+
+ dismiss(animated: true) {
+ UserLoggedIn(loginMethod: LOGIN_EMAIL)
+ }
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/11_AddAccount/AddAccountView.xib b/Messenger/Classes/Tabs/05_Settings/11_AddAccount/AddAccountView.xib
new file mode 100644
index 00000000..3ed81dde
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/11_AddAccount/AddAccountView.xib
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountCell.swift b/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountCell.swift
new file mode 100644
index 00000000..fba91a7e
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountCell.swift
@@ -0,0 +1,65 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class SwitchAccountCell: UITableViewCell {
+
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var labelName: UILabel!
+ @IBOutlet var labelEmail: UILabel!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bindData(account: [String: String]) {
+
+ labelName.text = account["fullname"]
+ labelEmail.text = account["email"]
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadImage(account: [String: String], tableView: UITableView, indexPath: IndexPath) {
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ if let picture = account["picture"] {
+ if let path = DownloadManager.pathImage(link: picture) {
+ imageUser.image = UIImage(contentsOfFile: path)
+ labelInitials.text = nil
+ } else {
+ imageUser.image = UIImage(named: "switchaccount_blank")
+ labelInitials.text = account["initials"]
+ downloadImage(picture: picture, tableView: tableView, indexPath: indexPath)
+ }
+ } else {
+ imageUser.image = UIImage(named: "switchaccount_blank")
+ labelInitials.text = account["initials"]
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func downloadImage(picture: String, tableView: UITableView, indexPath: IndexPath) {
+
+ DownloadManager.image(link: picture) { path, error, network in
+ if (error == nil) {
+ if (tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false) {
+ let cell = tableView.cellForRow(at: indexPath) as! SwitchAccountCell
+ cell.imageUser.image = UIImage(contentsOfFile: path!)
+ cell.labelInitials.text = nil
+ }
+ } else if ((error! as NSError).code == 102) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ self.downloadImage(picture: picture, tableView: tableView, indexPath: indexPath)
+ }
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountCell.xib b/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountCell.xib
new file mode 100644
index 00000000..5112ce5c
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountCell.xib
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountView.swift b/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountView.swift
new file mode 100644
index 00000000..9ce84243
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountView.swift
@@ -0,0 +1,66 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class SwitchAccountView: UIViewController, UITableViewDataSource, UITableViewDelegate {
+
+ @IBOutlet var tableView: UITableView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Switch Account"
+
+ navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel))
+
+ tableView.register(UINib(nibName: "SwitchAccountCell", bundle: nil), forCellReuseIdentifier: "SwitchAccountCell")
+
+ tableView.tableFooterView = UIView()
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCancel() {
+
+ dismiss(animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionSwitch(index: Int) {
+
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 1
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return Account.count()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountView.xib b/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountView.xib
new file mode 100644
index 00000000..33f77d1b
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/12_SwitchAccount/SwitchAccountView.xib
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Tabs/05_Settings/SettingsView.swift b/Messenger/Classes/Tabs/05_Settings/SettingsView.swift
new file mode 100644
index 00000000..170aaceb
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/SettingsView.swift
@@ -0,0 +1,346 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class SettingsView: UITableViewController {
+
+ @IBOutlet var viewHeader: UIView!
+ @IBOutlet var imageUser: UIImageView!
+ @IBOutlet var labelInitials: UILabel!
+ @IBOutlet var labelName: UILabel!
+ @IBOutlet var cellProfile: UITableViewCell!
+ @IBOutlet var cellPassword: UITableViewCell!
+ @IBOutlet var cellStatus: UITableViewCell!
+ @IBOutlet var cellBlocked: UITableViewCell!
+ @IBOutlet var cellArchive: UITableViewCell!
+ @IBOutlet var cellCache: UITableViewCell!
+ @IBOutlet var cellMedia: UITableViewCell!
+ @IBOutlet var cellWallpapers: UITableViewCell!
+ @IBOutlet var cellPrivacy: UITableViewCell!
+ @IBOutlet var cellTerms: UITableViewCell!
+ @IBOutlet var cellAddAccount: UITableViewCell!
+ @IBOutlet var cellSwitchAccount: UITableViewCell!
+ @IBOutlet var cellLogout: UITableViewCell!
+ @IBOutlet var cellLogoutAll: UITableViewCell!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
+
+ super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
+
+ tabBarItem.image = UIImage(named: "tab_settings")
+ tabBarItem.title = "Settings"
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(loadUser), name: NOTIFICATION_USER_LOGGED_IN)
+ NotificationCenterX.addObserver(target: self, selector: #selector(actionCleanup), name: NOTIFICATION_USER_LOGGED_OUT)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ required init?(coder aDecoder: NSCoder) {
+
+ super.init(coder: aDecoder)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ title = "Settings"
+
+ navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
+
+ imageUser.layer.cornerRadius = imageUser.frame.size.width / 2
+ imageUser.layer.masksToBounds = true
+
+ tableView.tableHeaderView = viewHeader
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidAppear(_ animated: Bool) {
+
+ super.viewDidAppear(animated)
+
+ if (FUser.currentId() != "") {
+ if FUser.isOnboardOk() {
+ loadUser()
+ } else {
+ OnboardUser(target: self)
+ }
+ } else {
+ LoginUser(target: self)
+ }
+ }
+
+ // MARK: - Backend actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func loadUser() {
+
+ let user = FUser.currentUser()
+
+ labelInitials.text = user.initials()
+ if let picture = user[FUSER_PICTURE] as? String {
+ DownloadManager.image(link: picture) { path, error, network in
+ if (error == nil) {
+ self.imageUser.image = UIImage(contentsOfFile: path!)
+ self.labelInitials.text = nil
+ }
+ }
+ }
+
+ labelName.text = user[FUSER_FULLNAME] as? String
+ cellStatus.textLabel?.text = user[FUSER_STATUS] as? String
+
+ tableView.reloadData()
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionProfile() {
+
+ let editProfileView = EditProfileView()
+ editProfileView.myInit(isOnboard: false)
+ let navController = NavigationController(rootViewController: editProfileView)
+ present(navController, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionPassword() {
+
+ let passwordView = PasswordView()
+ let navController = NavigationController(rootViewController: passwordView)
+ present(navController, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionStatus() {
+
+ let statusView = StatusView()
+ statusView.hidesBottomBarWhenPushed = true
+ navigationController?.pushViewController(statusView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionBlocked() {
+
+ let blockedView = BlockedView()
+ blockedView.hidesBottomBarWhenPushed = true
+ navigationController?.pushViewController(blockedView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionArchive() {
+
+ let archiveView = ArchiveView()
+ archiveView.hidesBottomBarWhenPushed = true
+ navigationController?.pushViewController(archiveView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionCache() {
+
+ let cacheView = CacheView()
+ cacheView.hidesBottomBarWhenPushed = true
+ navigationController?.pushViewController(cacheView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionMedia() {
+
+ let mediaView = MediaView()
+ mediaView.hidesBottomBarWhenPushed = true
+ navigationController?.pushViewController(mediaView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionWallpapers() {
+
+ let wallpapersView = WallpapersView()
+ let navController = NavigationController(rootViewController: wallpapersView)
+ present(navController, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionPrivacy() {
+
+ let privacyView = PrivacyView()
+ privacyView.hidesBottomBarWhenPushed = true
+ navigationController?.pushViewController(privacyView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionTerms() {
+
+ let termsView = TermsView()
+ termsView.hidesBottomBarWhenPushed = true
+ navigationController?.pushViewController(termsView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionAddAccount() {
+
+ AdvertPremium(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionSwitchAccount() {
+
+ AdvertPremium(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionLogout() {
+
+ let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+ alert.addAction(UIAlertAction(title: "Log out", style: .destructive, handler: { action in
+ self.actionLogoutUser()
+ }))
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+
+ present(alert, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionLogoutAll() {
+
+ let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+ alert.addAction(UIAlertAction(title: "Log out all accounts", style: .destructive, handler: { action in
+ self.actionLogoutAllUser()
+ }))
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+
+ present(alert, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionLogoutUser() {
+
+ LogoutUser(delAccount: DEL_ACCOUNT_ONE)
+
+ if (Account.count() == 0) {
+ tabBarController?.selectedIndex = Int(DEFAULT_TAB)
+ } else {
+ actionSwitchNextUser()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionSwitchNextUser() {
+
+ ProgressHUD.show(nil, interaction: false)
+
+ let userIds = Account.userIds()
+ if let userId = userIds.first {
+ let account = Account.account(userId: userId)
+
+ if let email = account["email"] {
+ if let password = account["password"] {
+ FUser.signIn(email: email, password: password) { user, error in
+ if (error == nil) {
+ UserLoggedIn(loginMethod: LOGIN_EMAIL)
+ } else {
+ ProgressHUD.showError(error!.localizedDescription)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionLogoutAllUser() {
+
+ LogoutUser(delAccount: DEL_ACCOUNT_ALL)
+ tabBarController?.selectedIndex = Int(DEFAULT_TAB)
+ }
+
+ // MARK: - Cleanup methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCleanup() {
+
+ imageUser.image = UIImage(named: "settings_blank")
+ labelName.text = nil
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 6
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ let emailLogin = (FUser.loginMethod() == LOGIN_EMAIL)
+
+ if (section == 0) { return emailLogin ? 2 : 1 }
+ if (section == 1) { return 1 }
+ if (section == 2) { return 5 }
+ if (section == 3) { return 2 }
+ if (section == 4) { return emailLogin ? 2 : 0 }
+ if (section == 5) { return (Account.count() > 1) ? 2 : 1 }
+
+ return 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+
+ if (section == 1) { return "Status" }
+
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { return cellProfile }
+ if (indexPath.section == 0) && (indexPath.row == 1) { return cellPassword }
+ if (indexPath.section == 1) && (indexPath.row == 0) { return cellStatus }
+ if (indexPath.section == 2) && (indexPath.row == 0) { return cellBlocked }
+ if (indexPath.section == 2) && (indexPath.row == 1) { return cellArchive }
+ if (indexPath.section == 2) && (indexPath.row == 2) { return cellCache }
+ if (indexPath.section == 2) && (indexPath.row == 3) { return cellMedia }
+ if (indexPath.section == 2) && (indexPath.row == 4) { return cellWallpapers }
+ if (indexPath.section == 3) && (indexPath.row == 0) { return cellPrivacy }
+ if (indexPath.section == 3) && (indexPath.row == 1) { return cellTerms }
+ if (indexPath.section == 4) && (indexPath.row == 0) { return cellAddAccount }
+ if (indexPath.section == 4) && (indexPath.row == 1) { return cellSwitchAccount }
+ if (indexPath.section == 5) && (indexPath.row == 0) { return cellLogout }
+ if (indexPath.section == 5) && (indexPath.row == 1) { return cellLogoutAll }
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Table view delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ if (indexPath.section == 0) && (indexPath.row == 0) { actionProfile() }
+ if (indexPath.section == 0) && (indexPath.row == 1) { actionPassword() }
+ if (indexPath.section == 1) && (indexPath.row == 0) { actionStatus() }
+ if (indexPath.section == 2) && (indexPath.row == 0) { actionBlocked() }
+ if (indexPath.section == 2) && (indexPath.row == 1) { actionArchive() }
+ if (indexPath.section == 2) && (indexPath.row == 2) { actionCache() }
+ if (indexPath.section == 2) && (indexPath.row == 3) { actionMedia() }
+ if (indexPath.section == 2) && (indexPath.row == 4) { actionWallpapers() }
+ if (indexPath.section == 3) && (indexPath.row == 0) { actionPrivacy() }
+ if (indexPath.section == 3) && (indexPath.row == 1) { actionTerms() }
+ if (indexPath.section == 4) && (indexPath.row == 0) { actionAddAccount() }
+ if (indexPath.section == 4) && (indexPath.row == 1) { actionSwitchAccount() }
+ if (indexPath.section == 5) && (indexPath.row == 0) { actionLogout() }
+ if (indexPath.section == 5) && (indexPath.row == 1) { actionLogoutAll() }
+ }
+}
diff --git a/Messenger/Classes/Tabs/05_Settings/SettingsView.xib b/Messenger/Classes/Tabs/05_Settings/SettingsView.xib
new file mode 100644
index 00000000..ea29f670
--- /dev/null
+++ b/Messenger/Classes/Tabs/05_Settings/SettingsView.xib
@@ -0,0 +1,345 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Utilities/advert/AdvertCustomView.swift b/Messenger/Classes/Utilities/advert/AdvertCustomView.swift
new file mode 100644
index 00000000..1611e9a9
--- /dev/null
+++ b/Messenger/Classes/Utilities/advert/AdvertCustomView.swift
@@ -0,0 +1,67 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class AdvertCustomView: UIViewController, MFMailComposeViewControllerDelegate {
+
+ @IBOutlet private var viewBox: UIView!
+ @IBOutlet private var imageIcon: UIImageView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+
+ imageIcon.layer.cornerRadius = 20
+ imageIcon.layer.masksToBounds = true
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ let rand = Int(arc4random_uniform(11) + 1)
+ let image = String(format: "advert%02d", rand)
+ imageIcon.image = UIImage(named: image)
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionContact(_ sender: Any) {
+
+ if (MFMailComposeViewController.canSendMail()) {
+ let mailCompose = MFMailComposeViewController()
+ mailCompose.setToRecipients(["info@relatedcode.com"])
+ mailCompose.setSubject("Custom development")
+ mailCompose.mailComposeDelegate = self
+ present(mailCompose, animated: true)
+ } else {
+ ProgressHUD.showError("Please configure your mail first.")
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionCancel(_ sender: Any) {
+
+ dismiss(animated: true)
+ }
+
+ // MARK: - MFMailComposeViewControllerDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
+
+ if (result == MFMailComposeResult.sent) {
+ ProgressHUD.showSuccess("Mail sent successfully.")
+ }
+ controller.dismiss(animated: true)
+ }
+}
diff --git a/Messenger/Classes/Utilities/advert/AdvertCustomView.xib b/Messenger/Classes/Utilities/advert/AdvertCustomView.xib
new file mode 100644
index 00000000..a7f4c614
--- /dev/null
+++ b/Messenger/Classes/Utilities/advert/AdvertCustomView.xib
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Utilities/advert/AdvertPremiumView.swift b/Messenger/Classes/Utilities/advert/AdvertPremiumView.swift
new file mode 100644
index 00000000..c3d24ff6
--- /dev/null
+++ b/Messenger/Classes/Utilities/advert/AdvertPremiumView.swift
@@ -0,0 +1,53 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class AdvertPremiumView: UIViewController {
+
+ @IBOutlet private var viewBox: UIView!
+ @IBOutlet private var imageIcon: UIImageView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+
+ imageIcon.layer.cornerRadius = 20
+ imageIcon.layer.masksToBounds = true
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ let rand = Int(arc4random_uniform(11) + 1)
+ let image = String(format: "advert%02d", rand)
+ imageIcon.image = UIImage(named: image)
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionPremium(_ sender: Any) {
+
+ dismiss(animated: true) {
+ if let url = URL(string: LINK_PREMIUM) {
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionCancel(_ sender: Any) {
+
+ dismiss(animated: true)
+ }
+}
diff --git a/Messenger/Classes/Utilities/advert/AdvertPremiumView.xib b/Messenger/Classes/Utilities/advert/AdvertPremiumView.xib
new file mode 100644
index 00000000..aa79c4d4
--- /dev/null
+++ b/Messenger/Classes/Utilities/advert/AdvertPremiumView.xib
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Utilities/advert/advert.swift b/Messenger/Classes/Utilities/advert/advert.swift
new file mode 100644
index 00000000..ba0d27a1
--- /dev/null
+++ b/Messenger/Classes/Utilities/advert/advert.swift
@@ -0,0 +1,28 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func AdvertCustom(target: Any) {
+
+ let viewController = target as! UIViewController
+ let advertCustomView = AdvertCustomView()
+ advertCustomView.modalPresentationStyle = .overFullScreen
+ viewController.present(advertCustomView, animated: true)
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func AdvertPremium(target: Any) {
+
+ let viewController = target as! UIViewController
+ let advertPremiumView = AdvertPremiumView()
+ advertPremiumView.modalPresentationStyle = .overFullScreen
+ viewController.present(advertPremiumView, animated: true)
+}
diff --git a/Messenger/Classes/Utilities/backend1/FObject.swift b/Messenger/Classes/Utilities/backend1/FObject.swift
new file mode 100644
index 00000000..cc4ae1e0
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend1/FObject.swift
@@ -0,0 +1,212 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class FObject: NSObject {
+
+ private var pathX: String!
+ private var subpathX: String?
+ var values: [String: Any] = [:]
+
+ // MARK: - Init methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ init(path: String, subpath: String?) {
+
+ super.init()
+
+ pathX = path
+ subpathX = subpath
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ convenience init(path: String) {
+
+ self.init(path: path, subpath: nil)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ convenience init(path: String, dictionary: [String: Any]) {
+
+ self.init(path: path, subpath: nil, dictionary: dictionary)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ convenience init(path: String, subpath: String?, dictionary: [String: Any]) {
+
+ self.init(path: path, subpath: subpath)
+
+ for (key, value) in dictionary {
+ values[key] = value
+ }
+ }
+
+ // MARK: - Accessors
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ subscript(key: String) -> Any? {
+
+ get {
+ return values[key]
+ }
+ set {
+ values[key] = newValue
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func objectId() -> String {
+
+ return values["objectId"] as! String
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func objectIdInit() {
+
+ if (values["objectId"] == nil) {
+ let reference = databaseReference()
+ values["objectId"] = reference.key
+ }
+ }
+
+ // MARK: - Save methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveInBackground() {
+
+ let reference = databaseReference()
+
+ if (values["objectId"] == nil) {
+ values["objectId"] = reference.key
+ }
+
+ if (values["createdAt"] == nil) {
+ values["createdAt"] = ServerValue.timestamp()
+ }
+
+ values["updatedAt"] = ServerValue.timestamp()
+
+ reference.updateChildValues(values)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveInBackground(block: @escaping (_ error: Error?) -> Void) {
+
+ let reference = databaseReference()
+
+ if (values["objectId"] == nil) {
+ values["objectId"] = reference.key
+ }
+
+ if (values["createdAt"] == nil) {
+ values["createdAt"] = ServerValue.timestamp()
+ }
+
+ values["updatedAt"] = ServerValue.timestamp()
+
+ reference.updateChildValues(values, withCompletionBlock: { error, ref in
+ block(error)
+ })
+ }
+
+ // MARK: - Update methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateInBackground() {
+
+ if (values["objectId"] != nil) {
+ values["updatedAt"] = ServerValue.timestamp()
+
+ let reference = databaseReference()
+ reference.updateChildValues(values)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateInBackground(block: @escaping (_ error: Error?) -> Void) {
+
+ if (values["objectId"] != nil) {
+ values["updatedAt"] = ServerValue.timestamp()
+
+ let reference = databaseReference()
+ reference.updateChildValues(values, withCompletionBlock: { error, ref in
+ block(error)
+ })
+ } else {
+ block(NSError.description("Object cannot be updated.", code: 101))
+ }
+ }
+
+ // MARK: - Delete methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func deleteInBackground() {
+
+ if (values["objectId"] != nil) {
+ let reference = databaseReference()
+ reference.removeValue()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func deleteInBackground(block: @escaping (_ error: Error?) -> Void) {
+
+ if (values["objectId"] != nil) {
+ let reference = databaseReference()
+ reference.removeValue(completionBlock: { error, ref in
+ block(error)
+ })
+ } else {
+ block(NSError.description("Object cannot be deleted.", code: 102))
+ }
+ }
+
+ // MARK: - Fetch methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func fetchInBackground() {
+
+ let reference = databaseReference()
+ reference.observeSingleEvent(of: DataEventType.value, with: { snapshot in
+ if (snapshot.exists()) {
+ self.values = snapshot.value as! [String: Any]
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func fetchInBackground(block: @escaping (_ error: Error?) -> Void) {
+
+ let reference = databaseReference()
+ reference.observeSingleEvent(of: DataEventType.value, with: { snapshot in
+ if (snapshot.exists()) {
+ self.values = snapshot.value as! [String: Any]
+ block(nil)
+ } else {
+ block(NSError.description("Object not found.", code: 103))
+ }
+ })
+ }
+
+ // MARK: - Private methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func databaseReference() -> DatabaseReference {
+
+ var reference: DatabaseReference!
+
+ if (subpathX == nil) {
+ reference = Database.database().reference(withPath: pathX)
+ } else {
+ reference = Database.database().reference(withPath: pathX).child(subpathX!)
+ }
+
+ if (values["objectId"] == nil) {
+ return reference.childByAutoId()
+ } else {
+ let objectId = values["objectId"] as! String
+ return reference.child(objectId)
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend1/FUser+Util.swift b/Messenger/Classes/Utilities/backend1/FUser+Util.swift
new file mode 100644
index 00000000..2933720c
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend1/FUser+Util.swift
@@ -0,0 +1,62 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+extension FUser {
+
+ // MARK: - Class methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func fullname() -> String { return FUser.currentUser().fullname() }
+ class func initials() -> String { return FUser.currentUser().initials() }
+ class func picture() -> String { return FUser.currentUser().picture() }
+ class func thumbnail() -> String { return FUser.currentUser().thumbnail() }
+ class func status() -> String { return FUser.currentUser().status() }
+ class func loginMethod() -> String { return FUser.currentUser().loginMethod() }
+ class func oneSignalId() -> String { return FUser.currentUser().oneSignalId() }
+
+ class func keepMedia() -> Int { return FUser.currentUser().keepMedia() }
+ class func networkImage() -> Int { return FUser.currentUser().networkImage() }
+ class func networkVideo() -> Int { return FUser.currentUser().networkVideo() }
+ class func networkAudio() -> Int { return FUser.currentUser().networkAudio() }
+
+ class func wallpaper() -> String { return FUser.currentUser().wallpaper() }
+ class func isOnboardOk() -> Bool { return FUser.currentUser().isOnboardOk() }
+
+ // MARK: - Instance methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func fullname() -> String { return (self[FUSER_FULLNAME] as? String) ?? "" }
+ func picture() -> String { return (self[FUSER_PICTURE] as? String) ?? "" }
+ func thumbnail() -> String { return (self[FUSER_THUMBNAIL] as? String) ?? "" }
+ func status() -> String { return (self[FUSER_STATUS] as? String) ?? "" }
+ func loginMethod() -> String { return (self[FUSER_LOGINMETHOD] as? String) ?? "" }
+ func oneSignalId() -> String { return (self[FUSER_ONESIGNALID] as? String) ?? "" }
+
+ func keepMedia() -> Int { return (self[FUSER_KEEPMEDIA] as? Int) ?? Int(KEEPMEDIA_FOREVER) }
+ func networkImage() -> Int { return (self[FUSER_NETWORKIMAGE] as? Int) ?? Int(NETWORK_ALL) }
+ func networkVideo() -> Int { return (self[FUSER_NETWORKVIDEO] as? Int) ?? Int(NETWORK_ALL) }
+ func networkAudio() -> Int { return (self[FUSER_NETWORKAUDIO] as? Int) ?? Int(NETWORK_ALL) }
+
+ func wallpaper() -> String { return (self[FUSER_WALLPAPER] as? String) ?? "" }
+ func isOnboardOk() -> Bool { return self[FUSER_FULLNAME] != nil }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func initials() -> String {
+
+ if let firstname = self[FUSER_FIRSTNAME] as? String {
+ if let lastname = self[FUSER_LASTNAME] as? String {
+ let initial1 = (firstname.count != 0) ? firstname.prefix(1) : ""
+ let initial2 = (lastname.count != 0) ? lastname.prefix(1) : ""
+ return "\(initial1)\(initial2)"
+ }
+ }
+ return ""
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend1/FUser.swift b/Messenger/Classes/Utilities/backend1/FUser.swift
new file mode 100644
index 00000000..c494ede7
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend1/FUser.swift
@@ -0,0 +1,215 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class FUser: FObject {
+
+ // MARK: - Class methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func currentId() -> String {
+
+ if let currentUser = Auth.auth().currentUser {
+ return currentUser.uid
+ }
+ return ""
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func currentUser() -> FUser {
+
+ if let dictionary = UserDefaults.standard.object(forKey: "CurrentUser") as? [String: Any] {
+ return FUser(path: "User", dictionary: dictionary)
+ }
+ return FUser(path: "User")
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func userWithId(userId: String) -> FUser {
+
+ let user = FUser(path: "User")
+ user["objectId"] = userId
+ return user
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func signIn(email: String, password: String, completion: @escaping (_ user: FUser?, _ error: Error?) -> Void) {
+
+ Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
+ if (error == nil) {
+ let firuser = authResult!.user
+ FUser.load(firuser: firuser) { user, error in
+ if (error == nil) {
+ completion(user, nil)
+ } else {
+ try? Auth.auth().signOut()
+ completion(nil, error)
+ }
+ }
+ } else {
+ completion(nil, error)
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createUser(email: String, password: String, completion: @escaping (_ user: FUser?, _ error: Error?) -> Void) {
+
+ Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
+ if (error == nil) {
+ let firuser = authResult!.user
+ FUser.create(uid: firuser.uid, email: email) { user, error in
+ if (error == nil) {
+ completion(user, nil)
+ } else {
+ firuser.delete(completion: { error in
+ if (error != nil) {
+ try? Auth.auth().signOut()
+ }
+ })
+ completion(nil, error)
+ }
+ }
+ } else {
+ completion(nil, error)
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func signIn(credential: AuthCredential, completion: @escaping (_ user: FUser?, _ error: Error?) -> Void) {
+
+ Auth.auth().signInAndRetrieveData(with: credential) { authResult, error in
+ if (error == nil) {
+ let firuser = authResult!.user
+ FUser.load(firuser: firuser) { user, error in
+ if (error == nil) {
+ completion(user, nil)
+ } else {
+ try? Auth.auth().signOut()
+ completion(nil, error)
+ }
+ }
+ } else {
+ completion(nil, error)
+ }
+ }
+ }
+
+ // MARK: - Logut methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func logOut() -> Bool {
+
+ do {
+ try Auth.auth().signOut()
+ UserDefaults.standard.removeObject(forKey: "CurrentUser")
+ UserDefaults.standard.synchronize()
+ return true
+ } catch {
+ return false
+ }
+ }
+
+ // MARK: - Private methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func load(firuser: User, completion: @escaping (_ user: FUser?, _ error: Error?) -> Void) {
+
+ let user = FUser.userWithId(userId: firuser.uid)
+
+ user.fetchInBackground(block: { error in
+ if (error != nil) {
+ self.create(uid: firuser.uid, email: firuser.email, completion: completion)
+ } else {
+ completion(user, nil)
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func create(uid: String, email: String?, completion: @escaping (_ user: FUser?, _ error: Error?) -> Void) {
+
+ let user = FUser.userWithId(userId: uid)
+
+ if (email != nil) {
+ user["email"] = email!
+ }
+
+ user.saveInBackground(block: { error in
+ if (error == nil) {
+ completion(user, nil)
+ } else {
+ completion(nil, error)
+ }
+ })
+ }
+
+ // MARK: - Instance methods
+ // MARK: - Current user methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func isCurrent() -> Bool {
+
+ if let objectId = self["objectId"] as? String {
+ return (objectId == FUser.currentId())
+ }
+ return false
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func saveLocalIfCurrent() {
+
+ if (isCurrent()) {
+ values.removeValue(forKey: "linkedIds")
+ UserDefaults.standard.set(values, forKey: "CurrentUser")
+ UserDefaults.standard.synchronize()
+ }
+ }
+
+ // MARK: - Save methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func saveInBackground() {
+
+ saveLocalIfCurrent()
+ super.saveInBackground()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func saveInBackground(block: @escaping (_ error: Error?) -> Void) {
+
+ saveLocalIfCurrent()
+ super.saveInBackground(block: { error in
+ if (error == nil) {
+ self.saveLocalIfCurrent()
+ }
+ block(error)
+ })
+ }
+
+ // MARK: - Fetch methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func fetchInBackground() {
+
+ super.fetchInBackground(block: { error in
+ if (error == nil) {
+ self.saveLocalIfCurrent()
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func fetchInBackground(block: @escaping (_ error: Error?) -> Void) {
+
+ super.fetchInBackground(block: { error in
+ if (error == nil) {
+ self.saveLocalIfCurrent()
+ }
+ block(error)
+ })
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend1/NSError+Util.swift b/Messenger/Classes/Utilities/backend1/NSError+Util.swift
new file mode 100644
index 00000000..5d826e96
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend1/NSError+Util.swift
@@ -0,0 +1,22 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+extension NSError {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func description(_ description: String, code: Int) -> Error? {
+
+ let domain = Bundle.main.bundleIdentifier ?? ""
+ let userInfo = [NSLocalizedDescriptionKey: description]
+ return NSError(domain: domain, code: code, userInfo: userInfo)
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend2/Blockeds.swift b/Messenger/Classes/Utilities/backend2/Blockeds.swift
new file mode 100644
index 00000000..2eb0c3d7
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend2/Blockeds.swift
@@ -0,0 +1,25 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Blockeds: NSObject {
+
+ private var timer: Timer?
+ private var refreshUIBlockeds = false
+ private var firebase: DatabaseReference?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: Blockeds = {
+ let instance = Blockeds()
+ return instance
+ } ()
+
+}
diff --git a/Messenger/Classes/Utilities/backend2/Blockers.swift b/Messenger/Classes/Utilities/backend2/Blockers.swift
new file mode 100644
index 00000000..9558d74d
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend2/Blockers.swift
@@ -0,0 +1,25 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Blockers: NSObject {
+
+ private var timer: Timer?
+ private var refreshUIBlockers = false
+ private var firebase: DatabaseReference?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: Blockers = {
+ let instance = Blockers()
+ return instance
+ } ()
+
+}
diff --git a/Messenger/Classes/Utilities/backend2/CallHistories.swift b/Messenger/Classes/Utilities/backend2/CallHistories.swift
new file mode 100644
index 00000000..352221fe
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend2/CallHistories.swift
@@ -0,0 +1,25 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class CallHistories: NSObject {
+
+ private var timer: Timer?
+ private var refreshUICallHistories = false
+ private var firebase: DatabaseReference?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: CallHistories = {
+ let instance = CallHistories()
+ return instance
+ } ()
+
+}
diff --git a/Messenger/Classes/Utilities/backend2/Friends.swift b/Messenger/Classes/Utilities/backend2/Friends.swift
new file mode 100644
index 00000000..ae90aea9
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend2/Friends.swift
@@ -0,0 +1,107 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Friends: NSObject {
+
+ private var timer: Timer?
+ private var refreshUIFriends = false
+ private var firebase: DatabaseReference?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: Friends = {
+ let instance = Friends()
+ return instance
+ } ()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init() {
+
+ super.init()
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(initObservers), name: NOTIFICATION_APP_STARTED)
+ NotificationCenterX.addObserver(target: self, selector: #selector(initObservers), name: NOTIFICATION_USER_LOGGED_IN)
+ NotificationCenterX.addObserver(target: self, selector: #selector(actionCleanup), name: NOTIFICATION_USER_LOGGED_OUT)
+
+ timer = Timer.scheduledTimer(timeInterval: 0.25, target: self, selector: #selector(refreshUserInterface), userInfo: nil, repeats: true)
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func initObservers() {
+
+ if (FUser.currentId() != "") {
+ if (firebase == nil) {
+ createObservers()
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func createObservers() {
+
+ let lastUpdatedAt = DBFriend.lastUpdatedAt()
+
+ firebase = Database.database().reference(withPath: FFRIEND_PATH).child(FUser.currentId())
+ let query = firebase?.queryOrdered(byChild: FFRIEND_UPDATEDAT).queryStarting(atValue: lastUpdatedAt + 1)
+
+ query?.observe(DataEventType.childAdded, with: { snapshot in
+ let friend = snapshot.value as! [String: Any]
+ if (friend[FFRIEND_CREATEDAT] as? Int64 != nil) {
+ DispatchQueue(label: "Friends").async {
+ self.updateRealm(friend: friend)
+ self.refreshUIFriends = true
+ }
+ }
+ })
+
+ query?.observe(DataEventType.childChanged, with: { snapshot in
+ let friend = snapshot.value as! [String: Any]
+ if (friend[FFRIEND_CREATEDAT] as? Int64 != nil) {
+ DispatchQueue(label: "Friends").async {
+ self.updateRealm(friend: friend)
+ self.refreshUIFriends = true
+ }
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateRealm(friend: [String: Any]) {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ DBFriend.createOrUpdate(in: realm, withValue: friend)
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ // MARK: - Cleanup methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCleanup() {
+
+ firebase?.removeAllObservers()
+ firebase = nil
+ }
+
+ // MARK: - Notification methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func refreshUserInterface() {
+
+ if (refreshUIFriends) {
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_FRIENDS)
+ refreshUIFriends = false
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend2/Groups.swift b/Messenger/Classes/Utilities/backend2/Groups.swift
new file mode 100644
index 00000000..ddf3fa16
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend2/Groups.swift
@@ -0,0 +1,26 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Groups: NSObject {
+
+ private var timer: Timer?
+ private var refreshUIGroups = false
+ private var refreshUIChats = false
+ private var firebase: DatabaseReference?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: Groups = {
+ let instance = Groups()
+ return instance
+ } ()
+
+}
diff --git a/Messenger/Classes/Utilities/backend2/Messages.swift b/Messenger/Classes/Utilities/backend2/Messages.swift
new file mode 100644
index 00000000..867a6a35
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend2/Messages.swift
@@ -0,0 +1,190 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Messages: NSObject {
+
+ var chatId = ""
+
+ private var timer: Timer?
+ private var refreshUIChats = false
+ private var refreshUIMessages1 = false
+ private var refreshUIMessages2 = false
+ private var playMessageIncoming = false
+ private var firebase: DatabaseReference?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: Messages = {
+ let instance = Messages()
+ return instance
+ } ()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func assignChatId(chatId: String) {
+
+ shared.chatId = chatId
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func resignChatId() {
+
+ shared.chatId = ""
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init() {
+
+ super.init()
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(initObservers), name: NOTIFICATION_APP_STARTED)
+ NotificationCenterX.addObserver(target: self, selector: #selector(initObservers), name: NOTIFICATION_USER_LOGGED_IN)
+ NotificationCenterX.addObserver(target: self, selector: #selector(actionCleanup), name: NOTIFICATION_USER_LOGGED_OUT)
+
+ timer = Timer.scheduledTimer(timeInterval: 0.25, target: self, selector: #selector(refreshUserInterface), userInfo: nil, repeats: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func initObservers() {
+
+ if (FUser.currentId() != "") {
+ if (firebase == nil) {
+ createObservers()
+ }
+ }
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func createObservers() {
+
+ let lastUpdatedAt = DBMessage.lastUpdatedAt()
+
+ firebase = Database.database().reference(withPath: FMESSAGE_PATH).child(FUser.currentId())
+ let query = firebase?.queryOrdered(byChild: FMESSAGE_UPDATEDAT).queryStarting(atValue: lastUpdatedAt + 1)
+
+ query?.observe(DataEventType.childAdded, with: { snapshot in
+ let message = snapshot.value as! [String: Any]
+ if (message[FMESSAGE_CREATEDAT] as? Int64 != nil) {
+ DispatchQueue(label: "Messages").async {
+ self.updateRealm(message: message)
+ self.updateChat(message: message)
+ self.playMessageIncoming(message: message)
+ self.refreshUserInterface1(message: message)
+ }
+ }
+ })
+
+ query?.observe(DataEventType.childChanged, with: { snapshot in
+ let message = snapshot.value as! [String: Any]
+ if (message[FMESSAGE_CREATEDAT] as? Int64 != nil) {
+ DispatchQueue(label: "Messages").async {
+ self.updateRealm(message: message)
+ self.updateChat(message: message)
+ self.refreshUserInterface2(message: message)
+ }
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateRealm(message: [String: Any]) {
+
+ var temp = message
+
+ let members = message[FMESSAGE_MEMBERS] as! [String]
+ let chatId = message[FMESSAGE_CHATID] as! String
+ let text = message[FMESSAGE_TEXT] as! String
+
+ temp[FMESSAGE_MEMBERS] = members.joined(separator: ",")
+ temp[FMESSAGE_TEXT] = Cryptor.decrypt(text: text, chatId: chatId)
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ DBMessage.createOrUpdate(in: realm, withValue: temp)
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateChat(message: [String: Any]) {
+
+ let chatId = message[FMESSAGE_CHATID] as! String
+
+ Chat.updateChat(chatId: chatId)
+ refreshUIChats = true
+ }
+
+ // MARK: - Cleanup methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCleanup() {
+
+ firebase?.removeAllObservers()
+ firebase = nil
+ }
+
+ // MARK: - Notification methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func refreshUserInterface1(message: [String: Any]) {
+
+ if (message[FMESSAGE_CHATID] as! String == chatId) {
+ refreshUIMessages1 = true
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func refreshUserInterface2(message: [String: Any]) {
+
+ if (message[FMESSAGE_CHATID] as! String == chatId) {
+ refreshUIMessages2 = true
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func playMessageIncoming(message: [String: Any]) {
+
+ if (message[FMESSAGE_CHATID] as! String == chatId) {
+ if (message[FMESSAGE_ISDELETED] as! Bool == false) {
+ if (message[FMESSAGE_TYPE] as! String != MESSAGE_STATUS) {
+ if (message[FMESSAGE_SENDERID] as! String != FUser.currentId()) {
+ playMessageIncoming = true
+ }
+ }
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func refreshUserInterface() {
+
+ if (refreshUIChats) {
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_CHATS)
+ refreshUIChats = false
+ }
+
+ if (refreshUIMessages1) {
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_MESSAGES1)
+ refreshUIMessages1 = false
+ }
+
+ if (refreshUIMessages2) {
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_MESSAGES2)
+ refreshUIMessages2 = false
+ }
+
+ if (playMessageIncoming) {
+ Audio.playMessageIncoming()
+ playMessageIncoming = false
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend2/Statuses.swift b/Messenger/Classes/Utilities/backend2/Statuses.swift
new file mode 100644
index 00000000..b128b5be
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend2/Statuses.swift
@@ -0,0 +1,107 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Statuses: NSObject {
+
+ private var timer: Timer?
+ private var refreshUIStatuses = false
+ private var firebase: DatabaseReference?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: Statuses = {
+ let instance = Statuses()
+ return instance
+ } ()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init() {
+
+ super.init()
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(initObservers), name: NOTIFICATION_APP_STARTED)
+ NotificationCenterX.addObserver(target: self, selector: #selector(initObservers), name: NOTIFICATION_USER_LOGGED_IN)
+ NotificationCenterX.addObserver(target: self, selector: #selector(actionCleanup), name: NOTIFICATION_USER_LOGGED_OUT)
+
+ timer = Timer.scheduledTimer(timeInterval: 0.25, target: self, selector: #selector(refreshUserInterface), userInfo: nil, repeats: true)
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func initObservers() {
+
+ if (FUser.currentId() != "") {
+ if (firebase == nil) {
+ createObservers()
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func createObservers() {
+
+ let lastUpdatedAt = DBStatus.lastUpdatedAt()
+
+ firebase = Database.database().reference(withPath: FSTATUS_PATH).child(FUser.currentId())
+ let query = firebase?.queryOrdered(byChild: FSTATUS_UPDATEDAT).queryStarting(atValue: lastUpdatedAt + 1)
+
+ query?.observe(DataEventType.childAdded, with: { snapshot in
+ let status = snapshot.value as! [String: Any]
+ if (status[FSTATUS_CREATEDAT] as? Int64 != nil) {
+ DispatchQueue(label: "Statuses").async {
+ self.updateRealm(status: status)
+ self.refreshUIStatuses = true
+ }
+ }
+ })
+
+ query?.observe(DataEventType.childChanged, with: { snapshot in
+ let status = snapshot.value as! [String: Any]
+ if (status[FSTATUS_CREATEDAT] as? Int64 != nil) {
+ DispatchQueue(label: "Statuses").async {
+ self.updateRealm(status: status)
+ self.refreshUIStatuses = true
+ }
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateRealm(status: [String: Any]) {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ DBStatus.createOrUpdate(in: realm, withValue: status)
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ // MARK: - Cleanup methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCleanup() {
+
+ firebase?.removeAllObservers()
+ firebase = nil
+ }
+
+ // MARK: - Notification methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func refreshUserInterface() {
+
+ if (refreshUIStatuses) {
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_STATUSES)
+ refreshUIStatuses = false
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend2/UserStatuses.swift b/Messenger/Classes/Utilities/backend2/UserStatuses.swift
new file mode 100644
index 00000000..4ebf8b6f
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend2/UserStatuses.swift
@@ -0,0 +1,103 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class UserStatuses: NSObject {
+
+ private var firebase: DatabaseReference?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: UserStatuses = {
+ let instance = UserStatuses()
+ return instance
+ } ()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init() {
+
+ super.init()
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(initObservers), name: NOTIFICATION_APP_STARTED)
+ NotificationCenterX.addObserver(target: self, selector: #selector(initObservers), name: NOTIFICATION_USER_LOGGED_IN)
+ NotificationCenterX.addObserver(target: self, selector: #selector(actionCleanup), name: NOTIFICATION_USER_LOGGED_OUT)
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func initObservers() {
+
+ if (FUser.currentId() != "") {
+ if (firebase == nil) {
+ checkItems()
+ createObservers()
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func checkItems() {
+
+ let reference = Database.database().reference(withPath: FUSERSTATUS_PATH)
+ reference.observeSingleEvent(of: DataEventType.value, with: { snapshot in
+ if (snapshot.exists() == false) {
+ UserStatus.createItems()
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func createObservers() {
+
+ let lastUpdatedAt = DBUserStatus.lastUpdatedAt()
+
+ firebase = Database.database().reference(withPath: FUSERSTATUS_PATH)
+ let query = firebase?.queryOrdered(byChild: FUSERSTATUS_UPDATEDAT).queryStarting(atValue: lastUpdatedAt + 1)
+
+ query?.observe(DataEventType.childAdded, with: { snapshot in
+ let userStatus = snapshot.value as! [String: Any]
+ if (userStatus[FUSERSTATUS_CREATEDAT] as? Int64 != nil) {
+ DispatchQueue(label: "UserStatuses").async {
+ self.updateRealm(userStatus: userStatus)
+ }
+ }
+ })
+
+ query?.observe(DataEventType.childChanged, with: { snapshot in
+ let userStatus = snapshot.value as! [String: Any]
+ if (userStatus[FUSERSTATUS_CREATEDAT] as? Int64 != nil) {
+ DispatchQueue(label: "UserStatuses").async {
+ self.updateRealm(userStatus: userStatus)
+ }
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateRealm(userStatus: [String: Any]) {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ DBUserStatus.createOrUpdate(in: realm, withValue: userStatus)
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ // MARK: - Cleanup methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCleanup() {
+
+ firebase?.removeAllObservers()
+ firebase = nil
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend2/Users.swift b/Messenger/Classes/Utilities/backend2/Users.swift
new file mode 100644
index 00000000..16d4119b
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend2/Users.swift
@@ -0,0 +1,106 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Users: NSObject {
+
+ private var timer: Timer?
+ private var refreshUIUsers = false
+ private var firebase: DatabaseReference?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: Users = {
+ let instance = Users()
+ return instance
+ } ()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init() {
+
+ super.init()
+
+ NotificationCenterX.addObserver(target: self, selector: #selector(initObservers), name: NOTIFICATION_APP_STARTED)
+ NotificationCenterX.addObserver(target: self, selector: #selector(initObservers), name: NOTIFICATION_USER_LOGGED_IN)
+ NotificationCenterX.addObserver(target: self, selector: #selector(actionCleanup), name: NOTIFICATION_USER_LOGGED_OUT)
+
+ timer = Timer.scheduledTimer(timeInterval: 0.25, target: self, selector: #selector(refreshUserInterface), userInfo: nil, repeats: true)
+ }
+
+ // MARK: - Backend methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func initObservers() {
+
+ if (FUser.currentId() != "") {
+ if (firebase == nil) {
+ createObservers()
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func createObservers() {
+
+ firebase = Database.database().reference(withPath: FUSER_PATH)
+ let child = "\(FUSER_LINKEDIDS)/\(FUser.currentId())"
+ let query = firebase?.queryOrdered(byChild: child).queryEqual(toValue: true)
+
+ query?.observe(DataEventType.childAdded, with: { snapshot in
+ let user = snapshot.value as! [String: Any]
+ if (user[FUSER_CREATEDAT] as? Int64 != nil) && (user[FUSER_FULLNAME] as? String != nil) {
+ DispatchQueue(label: "Users").async {
+ self.updateRealm(user: user)
+ self.refreshUIUsers = true
+ }
+ }
+ })
+
+ query?.observe(DataEventType.childChanged, with: { snapshot in
+ let user = snapshot.value as! [String: Any]
+ if (user[FUSER_CREATEDAT] as? Int64 != nil) && (user[FUSER_FULLNAME] as? String != nil) && (FUser.currentId() != "") {
+ DispatchQueue(label: "Users").async {
+ self.updateRealm(user: user)
+ self.refreshUIUsers = true
+ }
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func updateRealm(user: [String: Any]) {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ DBUser.createOrUpdate(in: realm, withValue: user)
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ // MARK: - Cleanup methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionCleanup() {
+
+ firebase?.removeAllObservers()
+ firebase = nil
+ }
+
+ // MARK: - Notification methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func refreshUserInterface() {
+
+ if (refreshUIUsers) {
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_USERS)
+ refreshUIUsers = false
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend3/Account.swift b/Messenger/Classes/Utilities/backend3/Account.swift
new file mode 100644
index 00000000..d5d58fed
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/Account.swift
@@ -0,0 +1,52 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Account: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func add(email: String, password: String) {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func update() {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func delOne() {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func delAll() {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func count() -> Int {
+
+ return 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func userIds() -> [String] {
+
+ return []
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func account(userId: String) -> [String: String] {
+
+ return [:]
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend3/Blocked.swift b/Messenger/Classes/Utilities/backend3/Blocked.swift
new file mode 100644
index 00000000..c9e3ab73
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/Blocked.swift
@@ -0,0 +1,30 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Blocked: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createItem(userId: String) {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func deleteItem(userId: String) {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func isBlocked(userId: String) -> Bool {
+
+ return false
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend3/Blocker.swift b/Messenger/Classes/Utilities/backend3/Blocker.swift
new file mode 100644
index 00000000..4efbd65f
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/Blocker.swift
@@ -0,0 +1,30 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Blocker: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createItem(userId: String) {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func deleteItem(userId: String) {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func isBlocker(userId: String) -> Bool {
+
+ return false
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend3/CallHistory.swift b/Messenger/Classes/Utilities/backend3/CallHistory.swift
new file mode 100644
index 00000000..a3eb6369
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/CallHistory.swift
@@ -0,0 +1,24 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class CallHistory: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createItem(userId: String, recipientId: String, name: String, details: SINCallDetails) {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func deleteItem(objectId: String) {
+
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend3/Chat.swift b/Messenger/Classes/Utilities/backend3/Chat.swift
new file mode 100644
index 00000000..71cd5af5
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/Chat.swift
@@ -0,0 +1,155 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Chat: NSObject {
+
+ // MARK: - Update methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func updateChat(chatId: String) {
+
+ let predicate = NSPredicate(format: "chatId == %@ AND isDeleted == NO", chatId)
+ let dbmessages = DBMessage.objects(with: predicate).sortedResults(usingKeyPath: FMESSAGE_CREATEDAT, ascending: true)
+
+ if let dbmessage = dbmessages.lastObject() as? DBMessage {
+ updateItem(dbmessage: dbmessage)
+ } else {
+ removeChat(chatId: chatId)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func removeChat(chatId: String) {
+
+ let predicate = NSPredicate(format: "chatId == %@", chatId)
+ if let dbchat = DBChat.objects(with: predicate).firstObject() as? DBChat {
+ deleteItem(dbchat: dbchat)
+ }
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func updateItem(dbmessage: DBMessage) {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+
+ let dbchat = fetchOrCreateItem(chatId: dbmessage.chatId)
+
+ let outgoing = (dbmessage.senderId == FUser.currentId())
+ let incoming = (dbmessage.senderId != FUser.currentId())
+
+ if (dbmessage.recipientId.count != 0) {
+ dbchat.groupId = ""
+ dbchat.recipientId = outgoing ? dbmessage.recipientId : dbmessage.senderId
+ dbchat.initials = outgoing ? dbmessage.recipientInitials : dbmessage.senderInitials
+ dbchat.picture = outgoing ? dbmessage.recipientPicture : dbmessage.senderPicture
+ dbchat.details = outgoing ? dbmessage.recipientName : dbmessage.senderName
+ }
+
+ if (dbmessage.groupId.count != 0) {
+ dbchat.recipientId = ""
+ dbchat.groupId = dbmessage.groupId
+ dbchat.initials = ""
+ dbchat.picture = dbmessage.groupPicture
+ dbchat.details = dbmessage.groupName
+ }
+
+ dbchat.lastMessage = dbmessage.text
+ dbchat.lastMessageDate = dbmessage.createdAt
+ if (incoming) {
+ dbchat.lastIncoming = dbmessage.createdAt
+ }
+
+ dbchat.isArchived = false
+ dbchat.isDeleted = false
+
+ dbchat.createdAt = Date().timestamp()
+ dbchat.updatedAt = Date().timestamp()
+
+ realm.addOrUpdate(dbchat)
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func fetchOrCreateItem(chatId: String) -> DBChat {
+
+ let predicate = NSPredicate(format: "chatId == %@", chatId)
+ if let dbchat = DBChat.objects(with: predicate).firstObject() as? DBChat {
+ return dbchat
+ }
+
+ let dbchat = DBChat()
+ dbchat.chatId = chatId
+ return dbchat
+ }
+
+ // MARK: - Delete, Archive methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func deleteItem(dbchat: DBChat) {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ dbchat.isDeleted = true
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func archiveItem(dbchat: DBChat) {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ dbchat.isArchived = true
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func unarchiveItem(dbchat: DBChat) {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ dbchat.isArchived = false
+ try realm.commitWriteTransaction()
+
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ // MARK: - ChatId methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func chatId(recipientId: String) -> String {
+
+ let currentId = FUser.currentId()
+ let members = [currentId, recipientId]
+ let sorted = members.sorted{$0.localizedCaseInsensitiveCompare($1) == .orderedAscending}
+ return Checksum.md5HashOf(string: sorted.joined(separator: ""))
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func chatId(groupId: String) -> String {
+
+ return Checksum.md5HashOf(string: groupId)
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend3/Friend.swift b/Messenger/Classes/Utilities/backend3/Friend.swift
new file mode 100644
index 00000000..765bb095
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/Friend.swift
@@ -0,0 +1,54 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Friend: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createItem(userId: String) {
+
+ let object = FObject(path: FFRIEND_PATH, subpath: FUser.currentId())
+
+ object[FFRIEND_OBJECTID] = userId
+ object[FFRIEND_FRIENDID] = userId
+ object[FFRIEND_ISDELETED] = false
+
+ object.saveInBackground(block: { error in
+ if (error != nil) {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func deleteItem(userId: String) {
+
+ let object = FObject(path: FFRIEND_PATH, subpath: FUser.currentId())
+
+ object[FFRIEND_OBJECTID] = userId
+ object[FFRIEND_ISDELETED] = true
+
+ object.updateInBackground(block: { error in
+ if (error != nil) {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func isFriend(userId: String) -> Bool {
+
+ let predicate = NSPredicate(format: "friendId == %@ AND isDeleted == NO", userId)
+ let dbfriend = DBFriend.objects(with: predicate).firstObject() as? DBFriend
+
+ return (dbfriend != nil)
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend3/Group.swift b/Messenger/Classes/Utilities/backend3/Group.swift
new file mode 100644
index 00000000..4130f285
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/Group.swift
@@ -0,0 +1,11 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
diff --git a/Messenger/Classes/Utilities/backend3/LinkedId.swift b/Messenger/Classes/Utilities/backend3/LinkedId.swift
new file mode 100644
index 00000000..ae4c1bf3
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/LinkedId.swift
@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class LinkedId: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createItem() {
+
+ let userId1 = FUser.currentId()
+
+ let firebase = Database.database().reference(withPath: FUSER_PATH).child(userId1).child(FUSER_LINKEDIDS)
+ firebase.updateChildValues([userId1: true])
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createItem(userId userId2: String) {
+
+ let userId1 = FUser.currentId()
+
+ let firebase1 = Database.database().reference(withPath: FUSER_PATH).child(userId1).child(FUSER_LINKEDIDS)
+ firebase1.updateChildValues([userId2: true])
+
+ let firebase2 = Database.database().reference(withPath: FUSER_PATH).child(userId2).child(FUSER_LINKEDIDS)
+ firebase2.updateChildValues([userId1: true])
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createItem(userId1: String, userId2: String) {
+
+ let firebase1 = Database.database().reference(withPath: FUSER_PATH).child(userId1).child(FUSER_LINKEDIDS)
+ firebase1.updateChildValues([userId2: true])
+
+ let firebase2 = Database.database().reference(withPath: FUSER_PATH).child(userId2).child(FUSER_LINKEDIDS)
+ firebase2.updateChildValues([userId1: true])
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend3/Message.swift b/Messenger/Classes/Utilities/backend3/Message.swift
new file mode 100644
index 00000000..5b0aa9d3
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/Message.swift
@@ -0,0 +1,58 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Message: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func deleteItem(dbmessage: DBMessage) {
+
+ if (dbmessage.status == TEXT_SENT) {
+ deleteItemSent(dbmessage: dbmessage)
+ }
+ if (dbmessage.status == TEXT_QUEUED) {
+ deleteItemQueued(dbmessage: dbmessage)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func deleteItemSent(dbmessage: DBMessage) {
+
+ let object = FObject(path: FMESSAGE_PATH, subpath: FUser.currentId())
+
+ object[FMESSAGE_OBJECTID] = dbmessage.objectId
+ object[FMESSAGE_ISDELETED] = true
+
+ object.updateInBackground(block: { error in
+ if (error != nil) {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func deleteItemQueued(dbmessage: DBMessage) {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ dbmessage.isDeleted = true
+ try realm.commitWriteTransaction()
+
+ Chat.updateChat(chatId: dbmessage.chatId)
+
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_MESSAGES1)
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_CHATS)
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend3/Status.swift b/Messenger/Classes/Utilities/backend3/Status.swift
new file mode 100644
index 00000000..8909df6f
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/Status.swift
@@ -0,0 +1,62 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Status: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func updateLastRead(chatId: String) {
+
+ let lastRead = ServerValue.timestamp()
+ let mutedUntil = self.mutedUntil(chatId: chatId)
+
+ let object = FObject(path: FSTATUS_PATH, subpath: FUser.currentId())
+
+ object[FSTATUS_OBJECTID] = chatId
+ object[FSTATUS_CHATID] = chatId
+ object[FSTATUS_LASTREAD] = lastRead
+ object[FSTATUS_MUTEDUNTIL] = mutedUntil
+
+ object.saveInBackground(block: { error in
+ if (error == nil) {
+ object.fetchInBackground(block: { error in
+ let firebase = Database.database().reference(withPath: FLASTREAD_PATH).child(chatId)
+ firebase.updateChildValues([FUser.currentId(): lastRead])
+ })
+ } else {
+ ProgressHUD.showError("Network error.")
+ }
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func updateMutedUntil(chatId: String, mutedUntil: Int64) {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func lastRead(chatId: String) -> Int64 {
+
+ let predicate = NSPredicate(format: "chatId == %@", chatId)
+ let dbstatus = DBStatus.objects(with: predicate).firstObject() as? DBStatus
+
+ return dbstatus?.lastRead ?? 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func mutedUntil(chatId: String) -> Int64 {
+
+ let predicate = NSPredicate(format: "chatId == %@", chatId)
+ let dbstatus = DBStatus.objects(with: predicate).firstObject() as? DBStatus
+
+ return dbstatus?.mutedUntil ?? 0
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend3/UserStatus.swift b/Messenger/Classes/Utilities/backend3/UserStatus.swift
new file mode 100644
index 00000000..881b4332
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend3/UserStatus.swift
@@ -0,0 +1,44 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class UserStatus: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createItems() {
+
+ createItem(name: "Available")
+ createItem(name: "Busy")
+ createItem(name: "At school")
+ createItem(name: "At the movies")
+ createItem(name: "At work")
+ createItem(name: "Battery about to die")
+ createItem(name: "Can't talk now")
+ createItem(name: "In a meeting")
+ createItem(name: "At the gym")
+ createItem(name: "Sleeping")
+ createItem(name: "Urgent calls only")
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createItem(name: String) {
+
+ let object = FObject(path: FUSERSTATUS_PATH)
+
+ object[FUSERSTATUS_NAME] = name
+
+ object.saveInBackground(block: { error in
+ if (error != nil) {
+ print("UserStatus createItem error: \(error)")
+ }
+ })
+ }
+}
diff --git a/Messenger/Classes/Utilities/backend4/push.swift b/Messenger/Classes/Utilities/backend4/push.swift
new file mode 100644
index 00000000..b2ccc200
--- /dev/null
+++ b/Messenger/Classes/Utilities/backend4/push.swift
@@ -0,0 +1,55 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func SendPushNotification1(message: FObject) {
+
+ let type = message[FMESSAGE_TYPE] as! String
+ var text = message[FMESSAGE_SENDERNAME] as! String
+ let chatId = message[FMESSAGE_CHATID] as! String
+
+ if (type == MESSAGE_TEXT) { text = text + (" sent you a text message.") }
+ if (type == MESSAGE_EMOJI) { text = text + (" sent you an emoji.") }
+ if (type == MESSAGE_PICTURE) { text = text + (" sent you a picture.") }
+ if (type == MESSAGE_VIDEO) { text = text + (" sent you a video.") }
+ if (type == MESSAGE_AUDIO) { text = text + (" sent you an audio.") }
+ if (type == MESSAGE_LOCATION) { text = text + (" sent you a location.") }
+
+ let firebase = Database.database().reference(withPath: FMUTEDUNTIL_PATH).child(chatId)
+ firebase.observeSingleEvent(of: DataEventType.value, with: { snapshot in
+ var userIds = message[FMESSAGE_MEMBERS] as! [String]
+
+
+ if let index = userIds.index(of: FUser.currentId()) {
+ userIds.remove(at: index)
+ }
+
+ SendPushNotification2(userIds: userIds, text: text)
+ })
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func SendPushNotification2(userIds: [String], text: String) {
+
+ let predicate = NSPredicate(format: "objectId IN %@", userIds)
+ let dbusers = DBUser.objects(with: predicate).sortedResults(usingKeyPath: FUSER_FULLNAME, ascending: true)
+
+ var oneSignalIds:[String] = []
+
+ for i in 0.. String {
+
+ return "last active: premium only"
+}
diff --git a/Messenger/Classes/Utilities/general1/Date+Util.swift b/Messenger/Classes/Utilities/general1/Date+Util.swift
new file mode 100644
index 00000000..9cbbdc9e
--- /dev/null
+++ b/Messenger/Classes/Utilities/general1/Date+Util.swift
@@ -0,0 +1,27 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+extension Date {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func timestamp() -> Int64 {
+
+ return Int64(self.timeIntervalSince1970 * 1000)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static func date(timestamp: Int64) -> Date {
+
+ let interval = TimeInterval(TimeInterval(timestamp) / 1000)
+ return Date(timeIntervalSince1970: interval)
+ }
+}
diff --git a/Messenger/Classes/Utilities/general1/NSDictionary+Util.swift b/Messenger/Classes/Utilities/general1/NSDictionary+Util.swift
new file mode 100644
index 00000000..7e229639
--- /dev/null
+++ b/Messenger/Classes/Utilities/general1/NSDictionary+Util.swift
@@ -0,0 +1,20 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+extension NSDictionary {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func name() -> String? {
+
+ return self["name"] as? String
+ }
+}
diff --git a/Messenger/Classes/Utilities/general1/NotificationCenterX.swift b/Messenger/Classes/Utilities/general1/NotificationCenterX.swift
new file mode 100644
index 00000000..83898e65
--- /dev/null
+++ b/Messenger/Classes/Utilities/general1/NotificationCenterX.swift
@@ -0,0 +1,40 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class NotificationCenterX: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func addObserver(target: Any, selector: Selector, name: String) {
+
+ NotificationCenter.default.addObserver(target, selector: selector, name: NSNotification.Name(name), object: nil)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func removeObserver(target: Any) {
+
+ NotificationCenter.default.removeObserver(target)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func post(notification: String) {
+
+ NotificationCenter.default.post(name: NSNotification.Name(notification), object: nil)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func post(notification: String, delay: TimeInterval) {
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
+ self.post(notification: notification)
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/general1/UserDefaultsX.swift b/Messenger/Classes/Utilities/general1/UserDefaultsX.swift
new file mode 100644
index 00000000..f13e76b1
--- /dev/null
+++ b/Messenger/Classes/Utilities/general1/UserDefaultsX.swift
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class UserDefaultsX: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func setObject(value: Any, key: String) {
+
+ UserDefaults.standard.set(value, forKey: key)
+ UserDefaults.standard.synchronize()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func removeObject(key: String) {
+
+ UserDefaults.standard.removeObject(forKey: key)
+ UserDefaults.standard.synchronize()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func removeObject(key: String, delay: TimeInterval) {
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
+ removeObject(key: key)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func object(key: String) -> Any? {
+
+ return UserDefaults.standard.object(forKey: key)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func string(key: String) -> String? {
+
+ return UserDefaults.standard.string(forKey: key)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func integer(key: String) -> Int {
+
+ return UserDefaults.standard.integer(forKey: key)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func bool(key: String) -> Bool {
+
+ return UserDefaults.standard.bool(forKey: key)
+ }
+}
diff --git a/Messenger/Classes/Utilities/general2/Connection.swift b/Messenger/Classes/Utilities/general2/Connection.swift
new file mode 100644
index 00000000..34decb82
--- /dev/null
+++ b/Messenger/Classes/Utilities/general2/Connection.swift
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Connection: NSObject {
+
+ var reachability: Reachability?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: Connection = {
+ let instance = Connection()
+ return instance
+ } ()
+
+ // MARK: - Reachability methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func isReachable() -> Bool {
+
+ return shared.reachability?.isReachable() ?? false
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func isReachableViaWWAN() -> Bool {
+
+ return shared.reachability?.isReachableViaWWAN() ?? false
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func isReachableViaWiFi() -> Bool {
+
+ return shared.reachability?.isReachableViaWiFi() ?? false
+ }
+
+ // MARK: - Instance methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init() {
+
+ super.init()
+
+ reachability = Reachability(hostname: "www.google.com")
+ reachability?.startNotifier()
+
+ let notification = NSNotification.Name.reachabilityChanged
+ NotificationCenterX.addObserver(target: self, selector: #selector(reachabilityChanged(_:)), name: notification.rawValue)
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func reachabilityChanged(_ notification: Notification?) {
+
+ }
+}
diff --git a/Messenger/Classes/Utilities/general2/Location.swift b/Messenger/Classes/Utilities/general2/Location.swift
new file mode 100644
index 00000000..9618fd67
--- /dev/null
+++ b/Messenger/Classes/Utilities/general2/Location.swift
@@ -0,0 +1,73 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Location: NSObject, CLLocationManagerDelegate {
+
+ var locationManager: CLLocationManager?
+ var coordinate = CLLocationCoordinate2D()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: Location = {
+ let instance = Location()
+ return instance
+ } ()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func start() {
+
+ shared.locationManager?.startUpdatingLocation()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func stop() {
+
+ shared.locationManager?.stopUpdatingLocation()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func latitude() -> CLLocationDegrees {
+
+ return shared.coordinate.latitude
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func longitude() -> CLLocationDegrees {
+
+ return shared.coordinate.longitude
+ }
+
+ // MARK: - Instance methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init() {
+
+ super.init()
+
+ locationManager = CLLocationManager()
+ locationManager?.delegate = self
+ locationManager?.desiredAccuracy = kCLLocationAccuracyBest
+ locationManager?.requestWhenInUseAuthorization()
+ }
+
+ // MARK: - CLLocationManagerDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
+
+ if let location = locations.last {
+ coordinate = location.coordinate
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
+
+ }
+}
diff --git a/Messenger/Classes/Utilities/general3/Audio.swift b/Messenger/Classes/Utilities/general3/Audio.swift
new file mode 100644
index 00000000..f4954ef7
--- /dev/null
+++ b/Messenger/Classes/Utilities/general3/Audio.swift
@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Audio: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func duration(path: String) -> Int {
+
+ let asset = AVURLAsset(url: URL(fileURLWithPath: path), options: nil)
+ return Int(round(CMTimeGetSeconds(asset.duration)))
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func playMessageIncoming() {
+
+ let path = Dir.application("rcmessage_incoming.aiff")
+ RCAudioPlayer.shared.playSound(path)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func playMessageOutgoing() {
+
+ let path = Dir.application("rcmessage_outgoing.aiff")
+ RCAudioPlayer.shared.playSound(path)
+ }
+}
diff --git a/Messenger/Classes/Utilities/general3/Checksum.swift b/Messenger/Classes/Utilities/general3/Checksum.swift
new file mode 100644
index 00000000..11007426
--- /dev/null
+++ b/Messenger/Classes/Utilities/general3/Checksum.swift
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Checksum: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func md5HashOf(data: Data) -> String {
+
+ var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
+ _ = digestData.withUnsafeMutableBytes {digestBytes in
+ data.withUnsafeBytes {messageBytes in
+ CC_MD5(messageBytes, CC_LONG((data.count)), digestBytes)
+ }
+ }
+
+ var md5 = ""
+ for byte in digestData {
+ md5 += String(format:"%02x", byte)
+ }
+
+ return md5
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func md5HashOf(path: String) -> String {
+
+ if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
+ return md5HashOf(data: data)
+ }
+ return ""
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func md5HashOf(string: String) -> String {
+
+ if let data = string.data(using: .utf8) {
+ return md5HashOf(data: data)
+ }
+ return ""
+ }
+}
diff --git a/Messenger/Classes/Utilities/general3/Cryptor.swift b/Messenger/Classes/Utilities/general3/Cryptor.swift
new file mode 100644
index 00000000..a866a14d
--- /dev/null
+++ b/Messenger/Classes/Utilities/general3/Cryptor.swift
@@ -0,0 +1,76 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Cryptor: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func encrypt(text: String, chatId: String) -> String? {
+
+ if let dataDecrypted = text.data(using: .utf8) {
+ if let dataEncrypted = encrypt(data: dataDecrypted, chatId: chatId) {
+ return dataEncrypted.base64EncodedString(options: [])
+ }
+ }
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func decrypt(text: String, chatId: String) -> String? {
+
+ if let dataEncrypted = Data(base64Encoded: text, options: []) {
+ if let dataDecrypted = decrypt(data: dataEncrypted, chatId: chatId) {
+ return String(data: dataDecrypted, encoding: .utf8)
+ }
+ }
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func encrypt(data: Data, chatId: String) -> Data? {
+
+ let password = Password.get(chatId: chatId)
+ return try? RNEncryptor.encryptData(data, with: kRNCryptorAES256Settings, password: password)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func decrypt(data: Data, chatId: String) -> Data? {
+
+ let password = Password.get(chatId: chatId)
+ return try? RNDecryptor.decryptData(data, withPassword: password)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func encrypt(path: String, chatId: String) {
+
+ do {
+ let dataDecrypted = try Data(contentsOf: URL(fileURLWithPath: path))
+ if let dataEncrypted = encrypt(data: dataDecrypted, chatId: chatId) {
+ do {
+ try dataEncrypted.write(to: URL(fileURLWithPath: path), options: .atomic)
+ } catch { print("Cryptor encryptFile error.") }
+ }
+ } catch { print("Cryptor encryptFile error.") }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func decrypt(path: String, chatId: String) {
+
+ do {
+ let dataEncrypted = try Data(contentsOf: URL(fileURLWithPath: path))
+ if let dataDecrypted = decrypt(data: dataEncrypted, chatId: chatId) {
+ do {
+ try dataDecrypted.write(to: URL(fileURLWithPath: path), options: .atomic)
+ } catch { print("Cryptor decryptFile error.") }
+ }
+ } catch { print("Cryptor decryptFile error.") }
+ }
+}
diff --git a/Messenger/Classes/Utilities/general3/Dir.swift b/Messenger/Classes/Utilities/general3/Dir.swift
new file mode 100644
index 00000000..82bd1264
--- /dev/null
+++ b/Messenger/Classes/Utilities/general3/Dir.swift
@@ -0,0 +1,101 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Dir: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func application() -> String {
+
+ return Bundle.main.resourcePath!
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func application(_ component: String?) -> String {
+
+ var path = application()
+
+ if (component != nil) { path = (path as NSString).appendingPathComponent(component!) }
+
+ return path
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func application(_ component1: String?, and component2: String?) -> String {
+
+ var path = application()
+
+ if (component1 != nil) { path = (path as NSString).appendingPathComponent(component1!) }
+ if (component2 != nil) { path = (path as NSString).appendingPathComponent(component2!) }
+
+ return path
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func document() -> String {
+
+ return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func document(_ component: String?) -> String {
+
+ var path = document()
+
+ if (component != nil) { path = (path as NSString).appendingPathComponent(component!) }
+
+ createIntermediate(path: path)
+
+ return path
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func cache() -> String {
+
+ return NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func cache(_ component: String?) -> String {
+
+ var path = cache()
+
+ if (component != nil) { path = (path as NSString).appendingPathComponent(component!) }
+
+ createIntermediate(path: path)
+
+ return path
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createIntermediate(path: String) {
+
+ let directory = (path as NSString).deletingLastPathComponent
+ if (exist(path: directory) == false) {
+ create(directory: directory)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func create(directory: String) {
+
+ try? FileManager.default.createDirectory(atPath: directory, withIntermediateDirectories: true, attributes: nil)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func exist(path: String) -> Bool {
+
+ return FileManager.default.fileExists(atPath: path)
+ }
+}
diff --git a/Messenger/Classes/Utilities/general3/Emoji.swift b/Messenger/Classes/Utilities/general3/Emoji.swift
new file mode 100644
index 00000000..b1d3f294
--- /dev/null
+++ b/Messenger/Classes/Utilities/general3/Emoji.swift
@@ -0,0 +1,36 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Emoji: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func isEmoji(text: String) -> Bool {
+
+ if (text.count == 1) {
+
+ let temp = NSString(string: text)
+ let high = unichar(temp.character(at: 0))
+
+ if ((0xd800 <= high) && (high <= 0xdbff)) {
+
+ let low = unichar(temp.character(at: 1))
+ let codepoint = (Int((high - 0xd800)) * 0x400) + Int((low - 0xdc00)) + 0x10000
+
+ return (0x1d000 <= codepoint) && (codepoint <= 0x1f77f)
+
+ } else {
+ return (0x2100 <= high) && (high <= 0x27bf)
+ }
+ }
+ return false
+ }
+}
diff --git a/Messenger/Classes/Utilities/general3/File.swift b/Messenger/Classes/Utilities/general3/File.swift
new file mode 100644
index 00000000..cc81a8b6
--- /dev/null
+++ b/Messenger/Classes/Utilities/general3/File.swift
@@ -0,0 +1,73 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class File: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func temp(ext: String) -> String {
+
+ let timestamp = Date().timestamp()
+ let file = "\(timestamp).\(ext)"
+ return Dir.cache(file)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func exist(path: String) -> Bool {
+
+ return FileManager.default.fileExists(atPath: path)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func remove(path: String) {
+
+ try? FileManager.default.removeItem(at: URL(fileURLWithPath: path))
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func copy(src: String, dest: String, overwrite: Bool) {
+
+ if (overwrite) { remove(path: dest) }
+
+ if (exist(path: dest) == false) {
+ try? FileManager.default.copyItem(atPath: src, toPath: dest)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func created(path: String) -> Date {
+
+ let attributes = try! FileManager.default.attributesOfItem(atPath: path)
+ return attributes[.creationDate] as! Date
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func modified(path: String) -> Date {
+
+ let attributes = try! FileManager.default.attributesOfItem(atPath: path)
+ return attributes[.modificationDate] as! Date
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func size(path: String) -> Int64 {
+
+ let attributes = try! FileManager.default.attributesOfItem(atPath: path)
+ return attributes[.size] as! Int64
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func diskFree() -> Int64 {
+
+ let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
+ let attributes = try! FileManager.default.attributesOfFileSystem(forPath: path)
+ return attributes[.systemFreeSize] as! Int64
+ }
+}
diff --git a/Messenger/Classes/Utilities/general3/Image.swift b/Messenger/Classes/Utilities/general3/Image.swift
new file mode 100644
index 00000000..90afa332
--- /dev/null
+++ b/Messenger/Classes/Utilities/general3/Image.swift
@@ -0,0 +1,57 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Image: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func square(image: UIImage, size: CGFloat) -> UIImage {
+
+ var cropped: UIImage!
+
+ if (image.size.width == image.size.height) {
+ cropped = image
+ } else if (image.size.width > image.size.height) {
+ let xpos: CGFloat = (image.size.width - image.size.height) / 2
+ cropped = crop(image: image, x: xpos, y: 0, width: image.size.height, height: image.size.height)
+ } else if (image.size.height > image.size.width) {
+ let ypos: CGFloat = (image.size.height - image.size.width) / 2
+ cropped = crop(image: image, x: 0, y: ypos, width: image.size.width, height: image.size.width)
+ }
+
+ return resize(image: cropped, width: size, height: size, scale: 1)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func resize(image: UIImage, width: CGFloat, height: CGFloat, scale: CGFloat) -> UIImage {
+
+ let size = CGSize(width: width, height: height)
+ let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
+
+ UIGraphicsBeginImageContextWithOptions(size, false, scale)
+ image.draw(in: rect)
+ let resized = UIGraphicsGetImageFromCurrentImageContext()!
+ UIGraphicsEndImageContext()
+
+ return resized
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func crop(image: UIImage, x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) -> UIImage {
+
+ let rect = CGRect(x: x, y: y, width: width, height: height)
+
+ let cgImage = image.cgImage?.cropping(to: rect)
+ let cropped = UIImage(cgImage: cgImage!)
+
+ return cropped
+ }
+}
diff --git a/Messenger/Classes/Utilities/general3/Password.swift b/Messenger/Classes/Utilities/general3/Password.swift
new file mode 100644
index 00000000..6c305ec4
--- /dev/null
+++ b/Messenger/Classes/Utilities/general3/Password.swift
@@ -0,0 +1,20 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Password: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func get(chatId: String) -> String {
+
+ return Checksum.md5HashOf(string: chatId)
+ }
+}
diff --git a/Messenger/Classes/Utilities/general3/Shortcut.swift b/Messenger/Classes/Utilities/general3/Shortcut.swift
new file mode 100644
index 00000000..a48344d7
--- /dev/null
+++ b/Messenger/Classes/Utilities/general3/Shortcut.swift
@@ -0,0 +1,25 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Shortcut: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func create() {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func update(userId: String) {
+
+ }
+
+}
diff --git a/Messenger/Classes/Utilities/general3/Video.swift b/Messenger/Classes/Utilities/general3/Video.swift
new file mode 100644
index 00000000..0b87aa2f
--- /dev/null
+++ b/Messenger/Classes/Utilities/general3/Video.swift
@@ -0,0 +1,39 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class Video: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func thumbnail(path: String) -> UIImage {
+
+ let asset = AVURLAsset(url: URL(fileURLWithPath: path), options: nil)
+ let generator = AVAssetImageGenerator(asset: asset)
+ generator.appliesPreferredTrackTransform = true
+
+ var time: CMTime = asset.duration
+ time.value = CMTimeValue(0)
+ var actualTime: CMTime = CMTimeMake(value: 0, timescale: 0)
+
+ if let cgImage = try? generator.copyCGImage(at: time, actualTime: &actualTime) {
+ return UIImage(cgImage: cgImage)
+ }
+
+ return UIImage()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func duration(path: String) -> Int {
+
+ let asset = AVURLAsset(url: URL(fileURLWithPath: path), options: nil)
+ return Int(round(CMTimeGetSeconds(asset.duration)))
+ }
+}
diff --git a/Messenger/Classes/Utilities/general4/camera.swift b/Messenger/Classes/Utilities/general4/camera.swift
new file mode 100644
index 00000000..f1384fa5
--- /dev/null
+++ b/Messenger/Classes/Utilities/general4/camera.swift
@@ -0,0 +1,176 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func PresentPhotoCamera(target: Any, edit: Bool) {
+
+ let type = kUTTypeImage as String
+
+ if (UIImagePickerController.isSourceTypeAvailable(.camera)) {
+ if let availableMediaTypes = UIImagePickerController.availableMediaTypes(for: .camera) {
+ if (availableMediaTypes.contains(type)) {
+
+ let imagePicker = UIImagePickerController()
+ imagePicker.mediaTypes = [type]
+ imagePicker.sourceType = .camera
+
+ if (UIImagePickerController.isCameraDeviceAvailable(.rear)) {
+ imagePicker.cameraDevice = .rear
+ } else if (UIImagePickerController.isCameraDeviceAvailable(.front)) {
+ imagePicker.cameraDevice = .front
+ }
+
+ let viewController = target as! UIViewController
+ imagePicker.allowsEditing = edit
+ imagePicker.showsCameraControls = true
+ imagePicker.delegate = viewController as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate)
+ viewController.present(imagePicker, animated: true)
+ }
+ }
+ }
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func PresentVideoCamera(target: Any, edit: Bool) {
+
+ let type = kUTTypeMovie as String
+
+ if (UIImagePickerController.isSourceTypeAvailable(.camera)) {
+ if let availableMediaTypes = UIImagePickerController.availableMediaTypes(for: .camera) {
+ if (availableMediaTypes.contains(type)) {
+
+ let imagePicker = UIImagePickerController()
+ imagePicker.mediaTypes = [type]
+ imagePicker.sourceType = .camera
+ imagePicker.videoMaximumDuration = TimeInterval(VIDEO_LENGTH)
+
+ if (UIImagePickerController.isCameraDeviceAvailable(.rear)) {
+ imagePicker.cameraDevice = .rear
+ } else if (UIImagePickerController.isCameraDeviceAvailable(.front)) {
+ imagePicker.cameraDevice = .front
+ }
+
+ let viewController = target as! UIViewController
+ imagePicker.allowsEditing = edit
+ imagePicker.showsCameraControls = true
+ imagePicker.delegate = viewController as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate)
+ viewController.present(imagePicker, animated: true)
+ }
+ }
+ }
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func PresentMultiCamera(target: Any, edit: Bool) {
+
+ let type1 = kUTTypeImage as String
+ let type2 = kUTTypeMovie as String
+
+ if (UIImagePickerController.isSourceTypeAvailable(.camera)) {
+ if let availableMediaTypes = UIImagePickerController.availableMediaTypes(for: .camera) {
+ if (availableMediaTypes.contains(type1) && availableMediaTypes.contains(type2)) {
+
+ let imagePicker = UIImagePickerController()
+ imagePicker.mediaTypes = [type1, type2]
+ imagePicker.sourceType = .camera
+ imagePicker.videoMaximumDuration = TimeInterval(VIDEO_LENGTH)
+
+ if (UIImagePickerController.isCameraDeviceAvailable(.rear)) {
+ imagePicker.cameraDevice = .rear
+ } else if (UIImagePickerController.isCameraDeviceAvailable(.front)) {
+ imagePicker.cameraDevice = .front
+ }
+
+ let viewController = target as! UIViewController
+ imagePicker.allowsEditing = edit
+ imagePicker.showsCameraControls = true
+ imagePicker.delegate = viewController as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate)
+ viewController.present(imagePicker, animated: true)
+ }
+ }
+ }
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func PresentPhotoLibrary(target: Any, edit: Bool) {
+
+ let type = kUTTypeImage as String
+
+ if (UIImagePickerController.isSourceTypeAvailable(.photoLibrary)) {
+ if let availableMediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) {
+ if (availableMediaTypes.contains(type)) {
+
+ let imagePicker = UIImagePickerController()
+ imagePicker.sourceType = .photoLibrary
+ imagePicker.mediaTypes = [type]
+
+ let viewController = target as! UIViewController
+ imagePicker.allowsEditing = edit
+ imagePicker.delegate = viewController as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate)
+ viewController.present(imagePicker, animated: true)
+ }
+ }
+ }
+ else if (UIImagePickerController.isSourceTypeAvailable(.savedPhotosAlbum)) {
+ if let availableMediaTypes = UIImagePickerController.availableMediaTypes(for: .savedPhotosAlbum) {
+ if (availableMediaTypes.contains(type)) {
+
+ let imagePicker = UIImagePickerController()
+ imagePicker.sourceType = .savedPhotosAlbum
+ imagePicker.mediaTypes = [type]
+
+ let viewController = target as! UIViewController
+ imagePicker.allowsEditing = edit
+ imagePicker.delegate = viewController as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate)
+ viewController.present(imagePicker, animated: true)
+ }
+ }
+ }
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func PresentVideoLibrary(target: Any, edit: Bool) {
+
+ let type = kUTTypeMovie as String
+
+ if (UIImagePickerController.isSourceTypeAvailable(.photoLibrary)) {
+ if let availableMediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) {
+ if (availableMediaTypes.contains(type)) {
+
+ let imagePicker = UIImagePickerController()
+ imagePicker.sourceType = .photoLibrary
+ imagePicker.mediaTypes = [type]
+ imagePicker.videoMaximumDuration = TimeInterval(VIDEO_LENGTH)
+
+ let viewController = target as! UIViewController
+ imagePicker.allowsEditing = edit
+ imagePicker.delegate = viewController as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate)
+ viewController.present(imagePicker, animated: true)
+ }
+ }
+ }
+ else if (UIImagePickerController.isSourceTypeAvailable(.savedPhotosAlbum)) {
+ if let availableMediaTypes = UIImagePickerController.availableMediaTypes(for: .savedPhotosAlbum) {
+ if (availableMediaTypes.contains(type)) {
+
+ let imagePicker = UIImagePickerController()
+ imagePicker.sourceType = .savedPhotosAlbum
+ imagePicker.mediaTypes = [type]
+ imagePicker.videoMaximumDuration = TimeInterval(VIDEO_LENGTH)
+
+ let viewController = target as! UIViewController
+ imagePicker.allowsEditing = edit
+ imagePicker.delegate = viewController as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate)
+ viewController.present(imagePicker, animated: true)
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/general4/converter.swift b/Messenger/Classes/Utilities/general4/converter.swift
new file mode 100644
index 00000000..9913593d
--- /dev/null
+++ b/Messenger/Classes/Utilities/general4/converter.swift
@@ -0,0 +1,57 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func Date2Short(date: Date) -> String {
+
+ return DateFormatter.localizedString(from: date, dateStyle: .short, timeStyle: .none)
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func Date2Medium(date: Date) -> String {
+
+ return DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .none)
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func Date2MediumTime(date: Date) -> String {
+
+ return DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .short)
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+func TimeElapsed(timestamp: Int64) -> String {
+
+ var elapsed = ""
+
+ let date = Date.date(timestamp: timestamp)
+ let seconds = Date().timeIntervalSince(date)
+
+ if (seconds < 60) {
+ elapsed = "Just now"
+ } else if (seconds < 60 * 60) {
+ let minutes = Int(seconds / 60)
+ let text = (minutes > 1) ? "mins" : "min"
+ elapsed = "\(minutes) \(text)"
+ } else if (seconds < 24 * 60 * 60) {
+ let hours = Int(seconds / (60 * 60))
+ let text = (hours > 1) ? "hours" : "hour"
+ elapsed = "\(hours) \(text)"
+ } else if (seconds < 7 * 24 * 60 * 60) {
+ let formatter = DateFormatter()
+ formatter.dateFormat = "EEEE"
+ elapsed = formatter.string(from: date)
+ } else {
+ elapsed = Date2Short(date: date)
+ }
+
+ return elapsed
+}
diff --git a/Messenger/Classes/Utilities/manager/CacheManager.swift b/Messenger/Classes/Utilities/manager/CacheManager.swift
new file mode 100644
index 00000000..42f09c17
--- /dev/null
+++ b/Messenger/Classes/Utilities/manager/CacheManager.swift
@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class CacheManager: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func cleanupExpired() {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func cleanupExpired(days: Int) {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func cleanupManual(logout: Bool) {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func total() -> Int64 {
+
+ return 0
+ }
+}
diff --git a/Messenger/Classes/Utilities/manager/DownloadManager.swift b/Messenger/Classes/Utilities/manager/DownloadManager.swift
new file mode 100644
index 00000000..b7018d58
--- /dev/null
+++ b/Messenger/Classes/Utilities/manager/DownloadManager.swift
@@ -0,0 +1,201 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DownloadManager: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func image(link: String, completion: @escaping (_ path: String?, _ error: Error?, _ network: Bool) -> Void) {
+
+ start(link: link, ext: "jpg", md5: nil, manual: false, completion: completion)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func image(link: String, md5: String?, completion: @escaping (_ path: String?, _ error: Error?, _ network: Bool) -> Void) {
+
+ start(link: link, ext: "jpg", md5: md5, manual: true, completion: completion)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func video(link: String, md5: String?, completion: @escaping (_ path: String?, _ error: Error?, _ network: Bool) -> Void) {
+
+ start(link: link, ext: "mp4", md5: md5, manual: true, completion: completion)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func audio(link: String, md5: String?, completion: @escaping (_ path: String?, _ error: Error?, _ network: Bool) -> Void) {
+
+ start(link: link, ext: "m4a", md5: md5, manual: true, completion: completion)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func start(link: String, ext: String, md5: String?, manual checkManual: Bool,
+ completion: @escaping (_ path: String?, _ error: Error?, _ network: Bool) -> Void) {
+
+ // Check if link is missing
+ //-----------------------------------------------------------------------------------------------------------------------------------------
+ if (link.count == 0) {
+ completion(nil, NSError.description("Missing link error.", code: 100), false)
+ return
+ }
+
+ //-----------------------------------------------------------------------------------------------------------------------------------------
+ let file = filename(link: link, ext: ext)
+
+ let path = Dir.document(file)
+ let manual = Dir.document(file + ".manual")
+ let loading = Dir.document(file + ".loading")
+
+ // Check if file is already downloaded
+ //-----------------------------------------------------------------------------------------------------------------------------------------
+ if (File.exist(path: path)) {
+ completion(path, nil, false)
+ return
+ }
+
+ // Check if manual download is required
+ //-----------------------------------------------------------------------------------------------------------------------------------------
+ if (checkManual) {
+ if (File.exist(path: manual)) {
+ completion(nil, NSError.description("Manual download required.", code: 101), false)
+ return
+ }
+ try? "manual".write(toFile: manual, atomically: false, encoding: .utf8)
+ }
+
+ // Check if file is currently downloading
+ //-----------------------------------------------------------------------------------------------------------------------------------------
+ let time = Int(Date().timeIntervalSince1970)
+
+ if (File.exist(path: loading)) {
+ if let temp = try? String(contentsOfFile: loading, encoding: .utf8) {
+ if let check = Int(temp) {
+ if (time - check < DOWNLOAD_TIMEOUT) {
+ completion(nil, NSError.description("Downloading.", code: 102), false)
+ return
+ }
+ }
+ }
+ }
+
+ try? "\(time)".write(toFile: loading, atomically: false, encoding: .utf8)
+
+ // Download the file
+ //-----------------------------------------------------------------------------------------------------------------------------------------
+ if let url = URL(string: link) {
+ let request = URLRequest(url: url)
+ let task = URLSession.shared.downloadTask(with: request, completionHandler: { location, response, error in
+ if (error == nil) {
+ do {
+ try FileManager.default.moveItem(at: location!, to: URL(fileURLWithPath: path))
+ if (File.size(path: path) != 0) {
+ if (md5 == nil) {
+ succeed(file: file, completion: completion)
+ } else {
+ if (md5 == Checksum.md5HashOf(path: path)) {
+ succeed(file: file, completion: completion)
+ } else { failed(file: file, error: NSError.description("MD5 checksum error.", code: 103), completion: completion) }
+ }
+ } else { failed(file: file, error: NSError.description("File lenght error.", code: 104), completion: completion) }
+ } catch { failed(file: file, error: NSError.description("File move error.", code: 105), completion: completion) }
+ } else { failed(file: file, error: error, completion: completion) }
+ })
+ task.resume()
+ } else { failed(file: file, error: NSError.description("Link URL error.", code: 106), completion: completion) }
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func succeed(file: String, completion: @escaping (_ path: String?, _ error: Error?, _ network: Bool) -> Void) {
+
+ let path = Dir.document(file)
+ let loading = Dir.document(file + ".loading")
+
+ File.remove(path: loading)
+
+ DispatchQueue.main.async {
+ completion(path, nil, true)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func failed(file: String, error: Error?, completion: @escaping (_ path: String?, _ error: Error?, _ network: Bool) -> Void) {
+
+ let path = Dir.document(file)
+ let loading = Dir.document(file + ".loading")
+
+ File.remove(path: path)
+ File.remove(path: loading)
+
+ DispatchQueue.main.async {
+ completion(nil, error, true)
+ }
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func fileImage(link: String) -> String { return filename(link: link, ext: "jpg") }
+ class func fileVideo(link: String) -> String { return filename(link: link, ext: "mp4") }
+ class func fileAudio(link: String) -> String { return filename(link: link, ext: "m4a") }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func filename(link: String, ext: String) -> String {
+
+ let file = Checksum.md5HashOf(string: link)
+ return "\(file).\(ext)"
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func pathImage(link: String) -> String? { return path(link: link, ext: "jpg") }
+ class func pathVideo(link: String) -> String? { return path(link: link, ext: "mp4") }
+ class func pathAudio(link: String) -> String? { return path(link: link, ext: "m4a") }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func path(link: String, ext: String) -> String? {
+
+ let file = filename(link: link, ext: ext)
+ let path = Dir.document(file)
+ if (File.exist(path: path)) {
+ return path
+ }
+ return nil
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func clearManualImage(link: String) { clearManual(link: link, ext: "jpg") }
+ class func clearManualVideo(link: String) { clearManual(link: link, ext: "mp4") }
+ class func clearManualAudio(link: String) { clearManual(link: link, ext: "m4a") }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func clearManual(link: String, ext: String) {
+
+ let file = filename(link: link, ext: ext)
+ let manual = Dir.document(file + ".manual")
+ File.remove(path: manual)
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func saveImage(data: Data, link: String) { saveData(data: data, file: fileImage(link: link)) }
+ class func saveVideo(data: Data, link: String) { saveData(data: data, file: fileVideo(link: link)) }
+ class func saveAudio(data: Data, link: String) { saveData(data: data, file: fileAudio(link: link)) }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func saveData(data: Data, file: String) {
+
+ do {
+ let path = Dir.document(file)
+ try data.write(to: URL(fileURLWithPath: path), options: .atomic)
+ } catch { print("DownloadManager saveData error.") }
+ }
+}
diff --git a/Messenger/Classes/Utilities/manager/MediaLoader.swift b/Messenger/Classes/Utilities/manager/MediaLoader.swift
new file mode 100644
index 00000000..4603b9e0
--- /dev/null
+++ b/Messenger/Classes/Utilities/manager/MediaLoader.swift
@@ -0,0 +1,178 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class MediaLoader: NSObject {
+
+ // MARK: - Picture public
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func loadPicture(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ if let path = DownloadManager.pathImage(link: dbmessage.picture) {
+ showPictureFile(rcmessage: rcmessage, path: path, tableView: tableView)
+ } else {
+ loadPictureMedia(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func loadPictureManual(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ }
+
+ // MARK: - Picture private
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func loadPictureMedia(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ let network = FUser.networkImage()
+
+ if (network == NETWORK_MANUAL) || ((network == NETWORK_WIFI) && (Connection.isReachableViaWiFi() == false)) {
+ rcmessage.status = Int(STATUS_MANUAL)
+ } else {
+ downloadPictureMedia(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func downloadPictureMedia(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ rcmessage.status = Int(STATUS_LOADING)
+
+ DownloadManager.image(link: dbmessage.picture, md5: dbmessage.pictureMD5) { path, error, network in
+ if (error == nil) {
+ if (network) {
+ Cryptor.decrypt(path: path!, chatId: dbmessage.chatId)
+ }
+ showPictureFile(rcmessage: rcmessage, path: path!, tableView: tableView)
+ } else {
+ rcmessage.status = Int(STATUS_MANUAL)
+ }
+ tableView.reloadData()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func showPictureFile(rcmessage: RCMessage, path: String, tableView: UITableView) {
+
+ rcmessage.picture_image = UIImage(contentsOfFile: path)
+ rcmessage.status = Int(STATUS_SUCCEED)
+ }
+
+ // MARK: - Video public
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func loadVideo(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ if let path = DownloadManager.pathVideo(link: dbmessage.video) {
+ showVideoFile(rcmessage: rcmessage, path: path, tableView: tableView)
+ } else {
+ loadVideoMedia(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func loadVideoManual(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ }
+
+ // MARK: - Video private
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func loadVideoMedia(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ let network = FUser.networkVideo()
+
+ if (network == NETWORK_MANUAL) || ((network == NETWORK_WIFI) && (Connection.isReachableViaWiFi() == false)) {
+ rcmessage.status = Int(STATUS_MANUAL)
+ } else {
+ downloadVideoMedia(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func downloadVideoMedia(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ rcmessage.status = Int(STATUS_LOADING)
+
+ DownloadManager.video(link: dbmessage.video, md5: dbmessage.videoMD5) { path, error, network in
+ if (error == nil) {
+ if (network) {
+ Cryptor.decrypt(path: path!, chatId: dbmessage.chatId)
+ }
+ showVideoFile(rcmessage: rcmessage, path: path!, tableView: tableView)
+ } else {
+ rcmessage.status = Int(STATUS_MANUAL)
+ }
+ tableView.reloadData()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func showVideoFile(rcmessage: RCMessage, path: String, tableView: UITableView) {
+
+ rcmessage.video_path = path
+ let picture = Video.thumbnail(path: path)
+ rcmessage.video_thumbnail = Image.square(image: picture, size: 320)
+ rcmessage.status = Int(STATUS_SUCCEED)
+ }
+
+ // MARK: - Audio public
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func loadAudio(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ if let path = DownloadManager.pathAudio(link: dbmessage.audio) {
+ showAudioFile(rcmessage: rcmessage, path: path, tableView: tableView)
+ } else {
+ loadAudioMedia(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func loadAudioManual(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ }
+
+ // MARK: - Audio private
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func loadAudioMedia(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ let network = FUser.networkAudio()
+
+ if (network == NETWORK_MANUAL) || ((network == NETWORK_WIFI) && (Connection.isReachableViaWiFi() == false)) {
+ rcmessage.status = Int(STATUS_MANUAL)
+ } else {
+ downloadAudioMedia(rcmessage: rcmessage, dbmessage: dbmessage, tableView: tableView)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func downloadAudioMedia(rcmessage: RCMessage, dbmessage: DBMessage, tableView: UITableView) {
+
+ rcmessage.status = Int(STATUS_LOADING)
+
+ DownloadManager.audio(link: dbmessage.audio, md5: dbmessage.audioMD5) { path, error, network in
+ if (error == nil) {
+ if (network) {
+ Cryptor.decrypt(path: path!, chatId: dbmessage.chatId)
+ }
+ showAudioFile(rcmessage: rcmessage, path: path!, tableView: tableView)
+ } else {
+ rcmessage.status = Int(STATUS_MANUAL)
+ }
+ tableView.reloadData()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func showAudioFile(rcmessage: RCMessage, path: String, tableView: UITableView) {
+
+ rcmessage.audio_path = path
+ rcmessage.status = Int(STATUS_SUCCEED)
+ }
+}
diff --git a/Messenger/Classes/Utilities/manager/RealmManager.swift b/Messenger/Classes/Utilities/manager/RealmManager.swift
new file mode 100644
index 00000000..62dc37c3
--- /dev/null
+++ b/Messenger/Classes/Utilities/manager/RealmManager.swift
@@ -0,0 +1,27 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RealmManager: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func cleanupDatabase() {
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ realm.deleteAllObjects()
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/manager/RelayManager.swift b/Messenger/Classes/Utilities/manager/RelayManager.swift
new file mode 100644
index 00000000..9655964d
--- /dev/null
+++ b/Messenger/Classes/Utilities/manager/RelayManager.swift
@@ -0,0 +1,70 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RelayManager: NSObject {
+
+ private var timer: Timer?
+ private var inProgress = false
+ private var dbmessages: RLMResults = DBMessage.objects(with: NSPredicate(value: false))
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: RelayManager = {
+ let instance = RelayManager()
+ return instance
+ } ()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init() {
+
+ super.init()
+
+ timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(relayMessages), userInfo: nil, repeats: true)
+
+ inProgress = false
+
+ let predicate = NSPredicate(format: "status == %@", TEXT_QUEUED)
+ dbmessages = DBMessage.objects(with: predicate).sortedResults(usingKeyPath: FMESSAGE_CREATEDAT, ascending: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func relayMessages() {
+
+ if (FUser.currentId() != "") {
+ if (Connection.isReachable()) {
+ if (inProgress == false) {
+ relayNextMessage()
+ }
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func relayNextMessage() {
+
+ if let dbmessage = dbmessages.firstObject() as? DBMessage {
+ inProgress = true
+ MessageRelay.send(dbmessage: dbmessage) { error in
+ if (error == nil) {
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ dbmessage.status = TEXT_SENT
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+ self.inProgress = false
+ }
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/manager/UploadManager.swift b/Messenger/Classes/Utilities/manager/UploadManager.swift
new file mode 100644
index 00000000..4746fe46
--- /dev/null
+++ b/Messenger/Classes/Utilities/manager/UploadManager.swift
@@ -0,0 +1,71 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class UploadManager: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func upload(data: Data, name: String, ext: String, completion: @escaping (_ link: String?, _ error: Error?) -> Void) {
+
+ let timestamp = Date().timestamp()
+ let child = "\(FUser.currentId())/\(name)/\(timestamp).\(ext)"
+
+ let reference = Storage.storage().reference(forURL: FIREBASE_STORAGE).child(child)
+ let task = reference.putData(data, metadata: nil, completion: nil)
+
+ task.observe(StorageTaskStatus.success, handler: { snapshot in
+ task.removeAllObservers()
+ reference.downloadURL(completion: { URL, error in
+ if (error == nil) {
+ completion(URL!.absoluteString, nil)
+ } else {
+ completion(nil, NSError.description("URL fetch failed.", code: 101))
+ }
+ })
+ })
+
+ task.observe(StorageTaskStatus.failure, handler: { snapshot in
+ task.removeAllObservers()
+ completion(nil, NSError.description("Upload failed.", code: 100))
+ })
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func upload(data: Data, name: String, ext: String, progress: @escaping (_ progress: Float) -> Void,
+ completion: @escaping (_ link: String?, _ error: Error?) -> Void) {
+
+ let timestamp = Date().timestamp()
+ let child = "\(FUser.currentId())/\(name)/\(timestamp).\(ext)"
+
+ let reference = Storage.storage().reference(forURL: FIREBASE_STORAGE).child(child)
+ let task = reference.putData(data, metadata: nil, completion: nil)
+
+ task.observe(StorageTaskStatus.progress, handler: { snapshot in
+ progress(Float(snapshot.progress!.completedUnitCount) / Float(snapshot.progress!.totalUnitCount))
+ })
+
+ task.observe(StorageTaskStatus.success, handler: { snapshot in
+ task.removeAllObservers()
+ reference.downloadURL(completion: { URL, error in
+ if (error == nil) {
+ completion(URL!.absoluteString, nil)
+ } else {
+ completion(nil, NSError.description("URL fetch failed.", code: 101))
+ }
+ })
+ })
+
+ task.observe(StorageTaskStatus.failure, handler: { snapshot in
+ task.removeAllObservers()
+ completion(nil, NSError.description("Upload failed.", code: 100))
+ })
+ }
+}
diff --git a/Messenger/Classes/Utilities/messages/MessageQueue.swift b/Messenger/Classes/Utilities/messages/MessageQueue.swift
new file mode 100644
index 00000000..8ef4fead
--- /dev/null
+++ b/Messenger/Classes/Utilities/messages/MessageQueue.swift
@@ -0,0 +1,277 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class MessageQueue: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func send(chatId: String, recipientId: String, status: String?, text: String?, picture: UIImage?, video: URL?, audio: String?) {
+
+ let predicate = NSPredicate(format: "objectId == %@", recipientId)
+ let dbuser = DBUser.objects(with: predicate).firstObject() as! DBUser
+
+ let senderPicture = FUser.thumbnail()
+ let recipientPicture = dbuser.thumbnail
+
+ let message = FObject(path: FMESSAGE_PATH)
+
+ message.objectIdInit()
+
+ message[FMESSAGE_CHATID] = chatId
+ message[FMESSAGE_MEMBERS] = [FUser.currentId(), recipientId]
+
+ message[FMESSAGE_SENDERID] = FUser.currentId()
+ message[FMESSAGE_SENDERNAME] = FUser.fullname()
+ message[FMESSAGE_SENDERINITIALS] = FUser.initials()
+ message[FMESSAGE_SENDERPICTURE] = senderPicture
+
+ message[FMESSAGE_RECIPIENTID] = recipientId
+ message[FMESSAGE_RECIPIENTNAME] = dbuser.fullname
+ message[FMESSAGE_RECIPIENTINITIALS] = dbuser.initials()
+ message[FMESSAGE_RECIPIENTPICTURE] = recipientPicture
+
+ message[FMESSAGE_GROUPID] = ""
+ message[FMESSAGE_GROUPNAME] = ""
+ message[FMESSAGE_GROUPPICTURE] = ""
+
+ message[FMESSAGE_TYPE] = ""
+ message[FMESSAGE_TEXT] = ""
+
+ message[FMESSAGE_PICTURE] = ""
+ message[FMESSAGE_PICTUREWIDTH] = 0
+ message[FMESSAGE_PICTUREHEIGHT] = 0
+ message[FMESSAGE_PICTUREMD5] = ""
+
+ message[FMESSAGE_VIDEO] = ""
+ message[FMESSAGE_VIDEODURATION] = 0
+ message[FMESSAGE_VIDEOMD5] = ""
+
+ message[FMESSAGE_AUDIO] = ""
+ message[FMESSAGE_AUDIODURATION] = 0
+ message[FMESSAGE_AUDIOMD5] = ""
+
+ message[FMESSAGE_LATITUDE] = 0
+ message[FMESSAGE_LONGITUDE] = 0
+
+ message[FMESSAGE_STATUS] = TEXT_QUEUED
+ message[FMESSAGE_ISDELETED] = false
+
+ let timestamp = Date().timestamp()
+ message[FMESSAGE_CREATEDAT] = timestamp
+ message[FMESSAGE_UPDATEDAT] = timestamp
+
+ if (status != nil) { sendStatusMessage(message: message, status: status!) }
+ else if (text != nil) { sendTextMessage(message: message, text: text!) }
+ else if (picture != nil) { sendPictureMessage(message: message, picture: picture!) }
+ else if (video != nil) { sendVideoMessage(message: message, video: video!) }
+ else if (audio != nil) { sendAudioMessage(message: message, audio: audio!) }
+ else { sendLoactionMessage(message: message) }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func send(chatId: String, groupId: String, status: String?, text: String?, picture: UIImage?, video: URL?, audio: String?) {
+
+ let predicate = NSPredicate(format: "objectId == %@", groupId)
+ let dbgroup = DBGroup.objects(with: predicate).firstObject() as! DBGroup
+
+ let senderPicture = FUser.thumbnail()
+ let groupPicture = dbgroup.picture
+
+ let message = FObject(path: FMESSAGE_PATH)
+
+ message.objectIdInit()
+
+ message[FMESSAGE_CHATID] = chatId
+ message[FMESSAGE_MEMBERS] = dbgroup.members.components(separatedBy: ",")
+
+ message[FMESSAGE_SENDERID] = FUser.currentId()
+ message[FMESSAGE_SENDERNAME] = FUser.fullname()
+ message[FMESSAGE_SENDERINITIALS] = FUser.initials()
+ message[FMESSAGE_SENDERPICTURE] = senderPicture
+
+ message[FMESSAGE_RECIPIENTID] = ""
+ message[FMESSAGE_RECIPIENTNAME] = ""
+ message[FMESSAGE_RECIPIENTINITIALS] = ""
+ message[FMESSAGE_RECIPIENTPICTURE] = ""
+
+ message[FMESSAGE_GROUPID] = groupId
+ message[FMESSAGE_GROUPNAME] = dbgroup.name
+ message[FMESSAGE_GROUPPICTURE] = groupPicture
+
+ message[FMESSAGE_TYPE] = ""
+ message[FMESSAGE_TEXT] = ""
+
+ message[FMESSAGE_PICTURE] = ""
+ message[FMESSAGE_PICTUREWIDTH] = 0
+ message[FMESSAGE_PICTUREHEIGHT] = 0
+ message[FMESSAGE_PICTUREMD5] = ""
+
+ message[FMESSAGE_VIDEO] = ""
+ message[FMESSAGE_VIDEODURATION] = 0
+ message[FMESSAGE_VIDEOMD5] = ""
+
+ message[FMESSAGE_AUDIO] = ""
+ message[FMESSAGE_AUDIODURATION] = 0
+ message[FMESSAGE_AUDIOMD5] = ""
+
+ message[FMESSAGE_LATITUDE] = 0
+ message[FMESSAGE_LONGITUDE] = 0
+
+ message[FMESSAGE_STATUS] = TEXT_QUEUED
+ message[FMESSAGE_ISDELETED] = false
+
+ let timestamp = Date().timestamp()
+ message[FMESSAGE_CREATEDAT] = timestamp
+ message[FMESSAGE_UPDATEDAT] = timestamp
+
+ if (status != nil) { sendStatusMessage(message: message, status: status!) }
+ else if (text != nil) { sendTextMessage(message: message, text: text!) }
+ else if (picture != nil) { sendPictureMessage(message: message, picture: picture!) }
+ else if (video != nil) { sendVideoMessage(message: message, video: video!) }
+ else if (audio != nil) { sendAudioMessage(message: message, audio: audio!) }
+ else { sendLoactionMessage(message: message) }
+ }
+
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func sendStatusMessage(message: FObject, status: String) {
+
+ message[FMESSAGE_TYPE] = MESSAGE_STATUS
+ message[FMESSAGE_TEXT] = status
+
+ createMessage(message: message)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func sendTextMessage(message: FObject, text: String) {
+
+ message[FMESSAGE_TYPE] = Emoji.isEmoji(text: text) ? MESSAGE_EMOJI : MESSAGE_TEXT
+ message[FMESSAGE_TEXT] = text
+
+ createMessage(message: message)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func sendPictureMessage(message: FObject, picture: UIImage) {
+
+ message[FMESSAGE_TYPE] = MESSAGE_PICTURE
+ message[FMESSAGE_TEXT] = "[Picture message]"
+
+ if let dataPicture = picture.jpegData(compressionQuality: 0.6) {
+ DownloadManager.saveImage(data: dataPicture, link: message.objectId())
+
+ message[FMESSAGE_PICTURE] = message.objectId()
+ message[FMESSAGE_PICTUREWIDTH] = Int(picture.size.width)
+ message[FMESSAGE_PICTUREHEIGHT] = Int(picture.size.height)
+
+ createMessage(message: message)
+ } else {
+ ProgressHUD.showError("Picture data error.")
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func sendVideoMessage(message: FObject, video: URL) {
+
+ message[FMESSAGE_TYPE] = MESSAGE_VIDEO
+ message[FMESSAGE_TEXT] = "[Video message]"
+
+ if let dataVideo = try? Data(contentsOf: video) {
+ DownloadManager.saveVideo(data: dataVideo, link: message.objectId())
+
+ message[FMESSAGE_VIDEO] = message.objectId()
+ message[FMESSAGE_VIDEODURATION] = Video.duration(path: video.path)
+
+ createMessage(message: message)
+ } else {
+ ProgressHUD.showError("Video data error.")
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func sendAudioMessage(message: FObject, audio: String) {
+
+ message[FMESSAGE_TYPE] = MESSAGE_AUDIO
+ message[FMESSAGE_TEXT] = "[Audio message]"
+
+ if let dataAudio = try? Data(contentsOf: URL(fileURLWithPath: audio)) {
+ DownloadManager.saveAudio(data: dataAudio, link: message.objectId())
+
+ message[FMESSAGE_AUDIO] = message.objectId()
+ message[FMESSAGE_AUDIODURATION] = Audio.duration(path: audio)
+
+ createMessage(message: message)
+ } else {
+ ProgressHUD.showError("Audio data error.")
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func sendLoactionMessage(message: FObject) {
+
+ message[FMESSAGE_TYPE] = MESSAGE_LOCATION
+ message[FMESSAGE_TEXT] = "[Location message]"
+
+ message[FMESSAGE_LATITUDE] = Location.latitude()
+ message[FMESSAGE_LONGITUDE] = Location.longitude()
+
+ createMessage(message: message)
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func createMessage(message: FObject) {
+
+ updateRealm(message: message.values)
+ updateChat(message: message.values)
+
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_MESSAGES1)
+ NotificationCenterX.post(notification: NOTIFICATION_REFRESH_CHATS)
+
+ playMessageOutgoing(message: message)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func updateRealm(message: [String: Any]) {
+
+ var temp = message
+
+ let members = message[FMESSAGE_MEMBERS] as! [String]
+ temp[FMESSAGE_MEMBERS] = members.joined(separator: ",")
+
+ do {
+ let realm = RLMRealm.default()
+ realm.beginWriteTransaction()
+ DBMessage.createOrUpdate(in: realm, withValue: temp)
+ try realm.commitWriteTransaction()
+ } catch {
+ ProgressHUD.showError("Realm commit error.")
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func updateChat(message: [String: Any]) {
+
+ let chatId = message[FMESSAGE_CHATID] as! String
+
+ Chat.updateChat(chatId: chatId)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func playMessageOutgoing(message: FObject) {
+
+ let type = message[FMESSAGE_TYPE] as! String
+
+ if (type != MESSAGE_STATUS) {
+ Audio.playMessageOutgoing()
+ }
+ }
+}
diff --git a/Messenger/Classes/Utilities/messages/MessageRelay.swift b/Messenger/Classes/Utilities/messages/MessageRelay.swift
new file mode 100644
index 00000000..5017737e
--- /dev/null
+++ b/Messenger/Classes/Utilities/messages/MessageRelay.swift
@@ -0,0 +1,161 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class MessageRelay: NSObject {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func send(dbmessage: DBMessage, completion: @escaping (_ error: Error?) -> Void) {
+
+ let message = FObject(path: FMESSAGE_PATH)
+
+ message[FMESSAGE_OBJECTID] = dbmessage.objectId
+
+ message[FMESSAGE_CHATID] = dbmessage.chatId
+ message[FMESSAGE_MEMBERS] = dbmessage.members.components(separatedBy: ",")
+
+ message[FMESSAGE_SENDERID] = dbmessage.senderId
+ message[FMESSAGE_SENDERNAME] = dbmessage.senderName
+ message[FMESSAGE_SENDERINITIALS] = dbmessage.senderInitials
+ message[FMESSAGE_SENDERPICTURE] = dbmessage.senderPicture
+
+ message[FMESSAGE_RECIPIENTID] = dbmessage.recipientId
+ message[FMESSAGE_RECIPIENTNAME] = dbmessage.recipientName
+ message[FMESSAGE_RECIPIENTINITIALS] = dbmessage.recipientInitials
+ message[FMESSAGE_RECIPIENTPICTURE] = dbmessage.recipientPicture
+
+ message[FMESSAGE_GROUPID] = dbmessage.groupId
+ message[FMESSAGE_GROUPNAME] = dbmessage.groupName
+ message[FMESSAGE_GROUPPICTURE] = dbmessage.groupPicture
+
+ message[FMESSAGE_TYPE] = dbmessage.type
+ message[FMESSAGE_TEXT] = Cryptor.encrypt(text: dbmessage.text, chatId: dbmessage.chatId)
+
+ message[FMESSAGE_PICTURE] = dbmessage.picture
+ message[FMESSAGE_PICTUREWIDTH] = dbmessage.pictureWidth
+ message[FMESSAGE_PICTUREHEIGHT] = dbmessage.pictureHeight
+ message[FMESSAGE_PICTUREMD5] = ""
+
+ message[FMESSAGE_VIDEO] = dbmessage.video
+ message[FMESSAGE_VIDEODURATION] = dbmessage.videoDuration
+ message[FMESSAGE_VIDEOMD5] = ""
+
+ message[FMESSAGE_AUDIO] = dbmessage.audio
+ message[FMESSAGE_AUDIODURATION] = dbmessage.audioDuration
+ message[FMESSAGE_AUDIOMD5] = ""
+
+ message[FMESSAGE_LATITUDE] = dbmessage.latitude
+ message[FMESSAGE_LONGITUDE] = dbmessage.longitude
+
+ message[FMESSAGE_STATUS] = TEXT_SENT
+ message[FMESSAGE_ISDELETED] = dbmessage.isDeleted
+
+ message[FMESSAGE_CREATEDAT] = dbmessage.createdAt
+ message[FMESSAGE_UPDATEDAT] = dbmessage.updatedAt
+
+ if (dbmessage.type == MESSAGE_TEXT) { sendMessage(message: message, completion: completion) }
+ if (dbmessage.type == MESSAGE_EMOJI) { sendMessage(message: message, completion: completion) }
+ if (dbmessage.type == MESSAGE_PICTURE) { sendPictureMessage(message: message, completion: completion) }
+ if (dbmessage.type == MESSAGE_VIDEO) { sendVideoMessage(message: message, completion: completion) }
+ if (dbmessage.type == MESSAGE_AUDIO) { sendAudioMessage(message: message, completion: completion) }
+ if (dbmessage.type == MESSAGE_LOCATION) { sendMessage(message: message, completion: completion) }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func sendPictureMessage(message: FObject, completion: @escaping (_ error: Error?) -> Void) {
+
+ let chatId = message[FMESSAGE_CHATID] as! String
+ let link = message[FMESSAGE_PICTURE] as! String
+
+ if let path = DownloadManager.pathImage(link: link) {
+ if let dataPicture = try? Data(contentsOf: URL(fileURLWithPath: path)) {
+ if let cryptedPicture = Cryptor.encrypt(data: dataPicture, chatId: chatId) {
+ let md5Picture = Checksum.md5HashOf(data: cryptedPicture)
+ UploadManager.upload(data: cryptedPicture, name: "message_image", ext: "jpg", completion: { link, error in
+ if (error == nil) {
+ DownloadManager.saveImage(data: dataPicture, link: link!)
+ message[FMESSAGE_PICTURE] = link
+ message[FMESSAGE_PICTUREMD5] = md5Picture
+ sendMessage(message: message, completion: completion)
+ } else { completion(NSError.description("Media upload error.", code: 100)) }
+ })
+ } else { completion(NSError.description("Media encryption error.", code: 101)) }
+ } else { completion(NSError.description("Media file error.", code: 102)) }
+ } else { completion(NSError.description("Missing media file.", code: 103)) }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func sendVideoMessage(message: FObject, completion: @escaping (_ error: Error?) -> Void) {
+
+ let chatId = message[FMESSAGE_CHATID] as! String
+ let link = message[FMESSAGE_VIDEO] as! String
+
+ if let path = DownloadManager.pathVideo(link: link) {
+ if let dataVideo = try? Data(contentsOf: URL(fileURLWithPath: path)) {
+ if let cryptedVideo = Cryptor.encrypt(data: dataVideo, chatId: chatId) {
+ let md5Video = Checksum.md5HashOf(data: cryptedVideo)
+ UploadManager.upload(data: cryptedVideo, name: "message_video", ext: "mp4", completion: { link, error in
+ if (error == nil) {
+ DownloadManager.saveVideo(data: dataVideo, link: link!)
+ message[FMESSAGE_VIDEO] = link
+ message[FMESSAGE_VIDEOMD5] = md5Video
+ sendMessage(message: message, completion: completion)
+ } else { completion(NSError.description("Media upload error.", code: 100)) }
+ })
+ } else { completion(NSError.description("Media encryption error.", code: 101)) }
+ } else { completion(NSError.description("Media file error.", code: 102)) }
+ } else { completion(NSError.description("Missing media file.", code: 103)) }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func sendAudioMessage(message: FObject, completion: @escaping (_ error: Error?) -> Void) {
+
+ let chatId = message[FMESSAGE_CHATID] as! String
+ let link = message[FMESSAGE_AUDIO] as! String
+
+ if let path = DownloadManager.pathAudio(link: link) {
+ if let dataAudio = try? Data(contentsOf: URL(fileURLWithPath: path)) {
+ if let cryptedAudio = Cryptor.encrypt(data: dataAudio, chatId: chatId) {
+ let md5Audio = Checksum.md5HashOf(data: cryptedAudio)
+ UploadManager.upload(data: cryptedAudio, name: "message_audio", ext: "m4a", completion: { link, error in
+ if (error == nil) {
+ DownloadManager.saveAudio(data: dataAudio, link: link!)
+ message[FMESSAGE_AUDIO] = link
+ message[FMESSAGE_AUDIOMD5] = md5Audio
+ sendMessage(message: message, completion: completion)
+ } else { completion(NSError.description("Media upload error.", code: 100)) }
+ })
+ } else { completion(NSError.description("Media encryption error.", code: 101)) }
+ } else { completion(NSError.description("Media file error.", code: 102)) }
+ } else { completion(NSError.description("Missing media file.", code: 103)) }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func sendMessage(message: FObject, completion: @escaping (_ error: Error?) -> Void) {
+
+ var multiple: [String: Any] = [:]
+
+ for userId in message[FMESSAGE_MEMBERS] as! [String] {
+ let path = "\(userId)/\(message.objectId())"
+ multiple[path] = message.values
+ }
+
+ let firebase = Database.database().reference(withPath: FMESSAGE_PATH)
+ firebase.updateChildValues(multiple, withCompletionBlock: { error, ref in
+ if (error == nil) {
+ SendPushNotification1(message: message);
+ completion(nil)
+ } else {
+ completion(NSError.description("Message sending failed.", code: 104))
+ }
+ })
+ }
+}
diff --git a/Messenger/Classes/Utilities/other/NYTPhotoItem.swift b/Messenger/Classes/Utilities/other/NYTPhotoItem.swift
new file mode 100644
index 00000000..50282dce
--- /dev/null
+++ b/Messenger/Classes/Utilities/other/NYTPhotoItem.swift
@@ -0,0 +1,23 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class NYTPhotoItem: NSObject, NYTPhoto {
+
+ var image: UIImage?
+ var imageData: Data?
+ var placeholderImage: UIImage?
+ var attributedCaptionTitle: NSAttributedString?
+ var attributedCaptionSummary: NSAttributedString?
+ var attributedCaptionCredit: NSAttributedString?
+
+ var objectId = ""
+}
diff --git a/Messenger/Classes/Utilities/realm/DBBlocked.swift b/Messenger/Classes/Utilities/realm/DBBlocked.swift
new file mode 100644
index 00000000..71d66dd5
--- /dev/null
+++ b/Messenger/Classes/Utilities/realm/DBBlocked.swift
@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DBBlocked: RLMObject {
+
+ @objc dynamic var objectId = ""
+
+ @objc dynamic var blockedId = ""
+ @objc dynamic var isDeleted = false
+
+ @objc dynamic var createdAt: Int64 = 0
+ @objc dynamic var updatedAt: Int64 = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func lastUpdatedAt() -> Int64 {
+
+ let dbblocked = DBBlocked.allObjects().sortedResults(usingKeyPath: "updatedAt", ascending: true).lastObject() as? DBBlocked
+ return dbblocked?.updatedAt ?? 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override static func primaryKey() -> String? {
+
+ return FBLOCKED_OBJECTID
+ }
+}
diff --git a/Messenger/Classes/Utilities/realm/DBBlocker.swift b/Messenger/Classes/Utilities/realm/DBBlocker.swift
new file mode 100644
index 00000000..35f71a49
--- /dev/null
+++ b/Messenger/Classes/Utilities/realm/DBBlocker.swift
@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DBBlocker: RLMObject {
+
+ @objc var objectId = ""
+
+ @objc var blockerId = ""
+ @objc var isDeleted = false
+
+ @objc var createdAt: Int64 = 0
+ @objc var updatedAt: Int64 = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func lastUpdatedAt() -> Int64 {
+
+ let dbblocker = DBBlocker.allObjects().sortedResults(usingKeyPath: "updatedAt", ascending: true).lastObject() as? DBBlocker
+ return dbblocker?.updatedAt ?? 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override static func primaryKey() -> String? {
+
+ return FBLOCKER_OBJECTID
+ }
+}
diff --git a/Messenger/Classes/Utilities/realm/DBCallHistory.swift b/Messenger/Classes/Utilities/realm/DBCallHistory.swift
new file mode 100644
index 00000000..2d8cf536
--- /dev/null
+++ b/Messenger/Classes/Utilities/realm/DBCallHistory.swift
@@ -0,0 +1,48 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DBCallHistory: RLMObject {
+
+ @objc dynamic var objectId = ""
+
+ @objc dynamic var initiatorId = ""
+ @objc dynamic var recipientId = ""
+ @objc dynamic var phoneNumber = ""
+
+ @objc dynamic var type = ""
+ @objc dynamic var text = ""
+
+ @objc dynamic var status = ""
+ @objc dynamic var duration: Int = 0
+
+ @objc dynamic var startedAt: Int64 = 0
+ @objc dynamic var establishedAt: Int64 = 0
+ @objc dynamic var endedAt: Int64 = 0
+
+ @objc dynamic var isDeleted = false
+
+ @objc dynamic var createdAt: Int64 = 0
+ @objc dynamic var updatedAt: Int64 = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func lastUpdatedAt() -> Int64 {
+
+ let dbcallhistory = DBCallHistory.allObjects().sortedResults(usingKeyPath: "updatedAt", ascending: true).lastObject() as? DBCallHistory
+ return dbcallhistory?.updatedAt ?? 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override static func primaryKey() -> String? {
+
+ return FCALLHISTORY_OBJECTID
+ }
+}
diff --git a/Messenger/Classes/Utilities/realm/DBChat.swift b/Messenger/Classes/Utilities/realm/DBChat.swift
new file mode 100644
index 00000000..b1921a29
--- /dev/null
+++ b/Messenger/Classes/Utilities/realm/DBChat.swift
@@ -0,0 +1,39 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DBChat: RLMObject {
+
+ @objc dynamic var chatId = ""
+
+ @objc dynamic var recipientId = ""
+ @objc dynamic var groupId = ""
+
+ @objc dynamic var initials = ""
+ @objc dynamic var picture = ""
+ @objc dynamic var details = ""
+
+ @objc dynamic var lastMessage = ""
+ @objc dynamic var lastMessageDate: Int64 = 0
+ @objc dynamic var lastIncoming: Int64 = 0
+
+ @objc dynamic var isArchived = false
+ @objc dynamic var isDeleted = false
+
+ @objc dynamic var createdAt: Int64 = 0
+ @objc dynamic var updatedAt: Int64 = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override static func primaryKey() -> String? {
+
+ return "chatId"
+ }
+}
diff --git a/Messenger/Classes/Utilities/realm/DBFriend.swift b/Messenger/Classes/Utilities/realm/DBFriend.swift
new file mode 100644
index 00000000..3cb44d97
--- /dev/null
+++ b/Messenger/Classes/Utilities/realm/DBFriend.swift
@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DBFriend: RLMObject {
+
+ @objc dynamic var objectId = ""
+
+ @objc dynamic var friendId = ""
+ @objc dynamic var isDeleted = false
+
+ @objc dynamic var createdAt: Int64 = 0
+ @objc dynamic var updatedAt: Int64 = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func lastUpdatedAt() -> Int64 {
+
+ let dbfriend = DBFriend.allObjects().sortedResults(usingKeyPath: "updatedAt", ascending: true).lastObject() as? DBFriend
+ return dbfriend?.updatedAt ?? 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override static func primaryKey() -> String? {
+
+ return FFRIEND_OBJECTID
+ }
+}
diff --git a/Messenger/Classes/Utilities/realm/DBGroup.swift b/Messenger/Classes/Utilities/realm/DBGroup.swift
new file mode 100644
index 00000000..e597296f
--- /dev/null
+++ b/Messenger/Classes/Utilities/realm/DBGroup.swift
@@ -0,0 +1,39 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DBGroup: RLMObject {
+
+ @objc dynamic var objectId = ""
+
+ @objc dynamic var userId = ""
+ @objc dynamic var name = ""
+ @objc dynamic var picture = ""
+ @objc dynamic var members = ""
+
+ @objc dynamic var isDeleted = false
+
+ @objc dynamic var createdAt: Int64 = 0
+ @objc dynamic var updatedAt: Int64 = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func lastUpdatedAt() -> Int64 {
+
+ let dbgroup = DBGroup.allObjects().sortedResults(usingKeyPath: "updatedAt", ascending: true).lastObject() as? DBGroup
+ return dbgroup?.updatedAt ?? 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override static func primaryKey() -> String? {
+
+ return FGROUP_OBJECTID
+ }
+}
diff --git a/Messenger/Classes/Utilities/realm/DBMessage.swift b/Messenger/Classes/Utilities/realm/DBMessage.swift
new file mode 100644
index 00000000..5fb3bdad
--- /dev/null
+++ b/Messenger/Classes/Utilities/realm/DBMessage.swift
@@ -0,0 +1,71 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DBMessage: RLMObject {
+
+ @objc dynamic var objectId = ""
+
+ @objc dynamic var chatId = ""
+ @objc dynamic var members = ""
+
+ @objc dynamic var senderId = ""
+ @objc dynamic var senderName = ""
+ @objc dynamic var senderInitials = ""
+ @objc dynamic var senderPicture = ""
+
+ @objc dynamic var recipientId = ""
+ @objc dynamic var recipientName = ""
+ @objc dynamic var recipientInitials = ""
+ @objc dynamic var recipientPicture = ""
+
+ @objc dynamic var groupId = ""
+ @objc dynamic var groupName = ""
+ @objc dynamic var groupPicture = ""
+
+ @objc dynamic var type = ""
+ @objc dynamic var text = ""
+
+ @objc dynamic var picture = ""
+ @objc dynamic var pictureWidth: Int = 0
+ @objc dynamic var pictureHeight: Int = 0
+ @objc dynamic var pictureMD5 = ""
+
+ @objc dynamic var video = ""
+ @objc dynamic var videoDuration: Int = 0
+ @objc dynamic var videoMD5 = ""
+
+ @objc dynamic var audio = ""
+ @objc dynamic var audioDuration: Int = 0
+ @objc dynamic var audioMD5 = ""
+
+ @objc dynamic var latitude: CLLocationDegrees = 0
+ @objc dynamic var longitude: CLLocationDegrees = 0
+
+ @objc dynamic var status = ""
+ @objc dynamic var isDeleted = false
+
+ @objc dynamic var createdAt: Int64 = 0
+ @objc dynamic var updatedAt: Int64 = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func lastUpdatedAt() -> Int64 {
+
+ let dbmessage = DBMessage.allObjects().sortedResults(usingKeyPath: "updatedAt", ascending: true).lastObject() as? DBMessage
+ return dbmessage?.updatedAt ?? 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override static func primaryKey() -> String? {
+
+ return FMESSAGE_OBJECTID
+ }
+}
diff --git a/Messenger/Classes/Utilities/realm/DBStatus.swift b/Messenger/Classes/Utilities/realm/DBStatus.swift
new file mode 100644
index 00000000..d0c57162
--- /dev/null
+++ b/Messenger/Classes/Utilities/realm/DBStatus.swift
@@ -0,0 +1,37 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DBStatus: RLMObject {
+
+ @objc dynamic var objectId = ""
+
+ @objc dynamic var chatId = ""
+
+ @objc dynamic var lastRead: Int64 = 0
+ @objc dynamic var mutedUntil: Int64 = 0
+
+ @objc dynamic var createdAt: Int64 = 0
+ @objc dynamic var updatedAt: Int64 = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func lastUpdatedAt() -> Int64 {
+
+ let dbstatus = DBStatus.allObjects().sortedResults(usingKeyPath: "updatedAt", ascending: true).lastObject() as? DBStatus
+ return dbstatus?.updatedAt ?? 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override static func primaryKey() -> String? {
+
+ return FSTATUS_OBJECTID
+ }
+}
diff --git a/Messenger/Classes/Utilities/realm/DBUser.swift b/Messenger/Classes/Utilities/realm/DBUser.swift
new file mode 100644
index 00000000..6cfd40eb
--- /dev/null
+++ b/Messenger/Classes/Utilities/realm/DBUser.swift
@@ -0,0 +1,66 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DBUser: RLMObject {
+
+ @objc dynamic var objectId = ""
+
+ @objc dynamic var email = ""
+ @objc dynamic var phone = ""
+
+ @objc dynamic var firstname = ""
+ @objc dynamic var lastname = ""
+ @objc dynamic var fullname = ""
+ @objc dynamic var country = ""
+ @objc dynamic var location = ""
+ @objc dynamic var status = ""
+
+ @objc dynamic var picture = ""
+ @objc dynamic var thumbnail = ""
+
+ @objc dynamic var keepMedia: Int = 0
+ @objc dynamic var networkImage: Int = 0
+ @objc dynamic var networkVideo: Int = 0
+ @objc dynamic var networkAudio: Int = 0
+ @objc dynamic var wallpaper = ""
+
+ @objc dynamic var loginMethod = ""
+ @objc dynamic var oneSignalId = ""
+
+ @objc dynamic var lastActive: Int64 = 0
+ @objc dynamic var lastTerminate: Int64 = 0
+
+ @objc dynamic var createdAt: Int64 = 0
+ @objc dynamic var updatedAt: Int64 = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func lastUpdatedAt() -> Int64 {
+
+ let dbuser = DBUser.allObjects().sortedResults(usingKeyPath: "updatedAt", ascending: true).lastObject() as? DBUser
+ return dbuser?.updatedAt ?? 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func initials() -> String {
+
+ let initial1 = (firstname.count != 0) ? firstname.prefix(1) : ""
+ let initial2 = (lastname.count != 0) ? lastname.prefix(1) : ""
+
+ return "\(initial1)\(initial2)"
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override static func primaryKey() -> String? {
+
+ return FUSER_OBJECTID
+ }
+}
diff --git a/Messenger/Classes/Utilities/realm/DBUserStatus.swift b/Messenger/Classes/Utilities/realm/DBUserStatus.swift
new file mode 100644
index 00000000..1788e3b3
--- /dev/null
+++ b/Messenger/Classes/Utilities/realm/DBUserStatus.swift
@@ -0,0 +1,34 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class DBUserStatus: RLMObject {
+
+ @objc dynamic var objectId = ""
+
+ @objc dynamic var name = ""
+
+ @objc dynamic var createdAt: Int64 = 0
+ @objc dynamic var updatedAt: Int64 = 0
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func lastUpdatedAt() -> Int64 {
+
+ let dbuserStatus = DBUserStatus.allObjects().sortedResults(usingKeyPath: "updatedAt", ascending: true).lastObject() as? DBUserStatus
+ return dbuserStatus?.updatedAt ?? 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override static func primaryKey() -> String? {
+
+ return FUSERSTATUS_OBJECTID
+ }
+}
diff --git a/Messenger/Classes/Utilities/utilities.h b/Messenger/Classes/Utilities/utilities.h
new file mode 100644
index 00000000..8aac782d
--- /dev/null
+++ b/Messenger/Classes/Utilities/utilities.h
@@ -0,0 +1,79 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef app_utilities_h
+#define app_utilities_h
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+#pragma mark -
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import "SinchService.h"
+#import
+
+#pragma mark -
+
+#import "MBProgressHUD/MBProgressHUD.h"
+#import "MGSwipeTableCell/MGSwipeTableCell.h"
+#import "NYTPhotoViewer/NYTPhotoViewer.h"
+#import "ProgressHUD/ProgressHUD.h"
+#import "Reachability/Reachability.h"
+#import "RNCryptor_objc/RNDecryptor.h"
+#import "RNCryptor_objc/RNEncryptor.h"
+#import "SoundManager/SoundManager.h"
+
+#pragma mark -
+
+#import "AppConstant.h"
+
+#pragma mark - advert
+
+#pragma mark - backend1
+
+#pragma mark - backend2
+
+#pragma mark - backend3
+
+#pragma mark - backend4
+
+#pragma mark - general1
+
+#pragma mark - general2
+
+#pragma mark - general3
+
+#pragma mark - general4
+
+#pragma mark - manager
+
+#pragma mark - messages
+
+#pragma mark - other
+
+#pragma mark - realm
+
+#endif
diff --git a/Messenger/Classes/Welcome/LoginEmail/LoginEmailView.swift b/Messenger/Classes/Welcome/LoginEmail/LoginEmailView.swift
new file mode 100644
index 00000000..0d53a173
--- /dev/null
+++ b/Messenger/Classes/Welcome/LoginEmail/LoginEmailView.swift
@@ -0,0 +1,94 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc protocol LoginEmailDelegate: class {
+
+ func didLoginEmail()
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class LoginEmailView: UIViewController, UITextFieldDelegate {
+
+ @IBOutlet weak var delegate: LoginEmailDelegate?
+
+ @IBOutlet var fieldEmail: UITextField!
+ @IBOutlet var fieldPassword: UITextField!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+
+ let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
+ view.addGestureRecognizer(gestureRecognizer)
+ gestureRecognizer.cancelsTouchesInView = false
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+
+ dismissKeyboard()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func dismissKeyboard() {
+
+ view.endEditing(true)
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionLogin(_ sender: Any) {
+
+ var email = (fieldEmail.text ?? "").lowercased()
+ var password = fieldPassword.text ?? ""
+
+ if (email.count == 0) { ProgressHUD.showError("Please enter your email."); return }
+ if (password.count == 0) { ProgressHUD.showError("Please enter your password."); return }
+
+ LogoutUser(delAccount: DEL_ACCOUNT_NONE)
+
+ ProgressHUD.show(nil, interaction: false)
+
+ FUser.signIn(email: email, password: password) { user, error in
+ if (error == nil) {
+ Account.add(email: email, password: password)
+ self.dismiss(animated: true) {
+ self.delegate?.didLoginEmail()
+ }
+ } else {
+ ProgressHUD.showError(error!.localizedDescription)
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionDismiss(_ sender: Any) {
+
+ dismiss(animated: true)
+ }
+
+ // MARK: - UITextField delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+
+ if (textField == fieldEmail) {
+ fieldPassword.becomeFirstResponder()
+ }
+ if (textField == fieldPassword) {
+ actionLogin(0)
+ }
+ return true
+ }
+}
diff --git a/Messenger/Classes/Welcome/LoginEmail/LoginEmailView.xib b/Messenger/Classes/Welcome/LoginEmail/LoginEmailView.xib
new file mode 100644
index 00000000..40f31970
--- /dev/null
+++ b/Messenger/Classes/Welcome/LoginEmail/LoginEmailView.xib
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Welcome/LoginGoogle/LoginGoogleView.swift b/Messenger/Classes/Welcome/LoginGoogle/LoginGoogleView.swift
new file mode 100644
index 00000000..8ce59ba9
--- /dev/null
+++ b/Messenger/Classes/Welcome/LoginGoogle/LoginGoogleView.swift
@@ -0,0 +1,17 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc protocol LoginGoogleDelegate: class {
+
+ func didLoginGoogle()
+}
+
diff --git a/Messenger/Classes/Welcome/LoginGoogle/LoginGoogleView.xib b/Messenger/Classes/Welcome/LoginGoogle/LoginGoogleView.xib
new file mode 100644
index 00000000..e6861893
--- /dev/null
+++ b/Messenger/Classes/Welcome/LoginGoogle/LoginGoogleView.xib
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Welcome/LoginPhone/01_VerifySMS/VerifySMSView.swift b/Messenger/Classes/Welcome/LoginPhone/01_VerifySMS/VerifySMSView.swift
new file mode 100644
index 00000000..2390c3f0
--- /dev/null
+++ b/Messenger/Classes/Welcome/LoginPhone/01_VerifySMS/VerifySMSView.swift
@@ -0,0 +1,18 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc protocol VerifySMSDelegate: class {
+
+ func verifySMSSucceed()
+ func verifySMSFailed()
+}
+
diff --git a/Messenger/Classes/Welcome/LoginPhone/01_VerifySMS/VerifySMSView.xib b/Messenger/Classes/Welcome/LoginPhone/01_VerifySMS/VerifySMSView.xib
new file mode 100755
index 00000000..c1c4e6a2
--- /dev/null
+++ b/Messenger/Classes/Welcome/LoginPhone/01_VerifySMS/VerifySMSView.xib
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Welcome/LoginPhone/LoginPhoneView.swift b/Messenger/Classes/Welcome/LoginPhone/LoginPhoneView.swift
new file mode 100644
index 00000000..1a181559
--- /dev/null
+++ b/Messenger/Classes/Welcome/LoginPhone/LoginPhoneView.swift
@@ -0,0 +1,17 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc protocol LoginPhoneDelegate: class {
+
+ func didLoginPhone()
+}
+
diff --git a/Messenger/Classes/Welcome/LoginPhone/LoginPhoneView.xib b/Messenger/Classes/Welcome/LoginPhone/LoginPhoneView.xib
new file mode 100755
index 00000000..1af53178
--- /dev/null
+++ b/Messenger/Classes/Welcome/LoginPhone/LoginPhoneView.xib
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Welcome/RegisterEmail/RegisterEmailView.swift b/Messenger/Classes/Welcome/RegisterEmail/RegisterEmailView.swift
new file mode 100644
index 00000000..3af941e9
--- /dev/null
+++ b/Messenger/Classes/Welcome/RegisterEmail/RegisterEmailView.swift
@@ -0,0 +1,94 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc protocol RegisterEmailDelegate: class {
+
+ func didRegisterUser()
+}
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RegisterEmailView: UIViewController, UITextFieldDelegate {
+
+ @IBOutlet weak var delegate: RegisterEmailDelegate?
+
+ @IBOutlet var fieldEmail: UITextField!
+ @IBOutlet var fieldPassword: UITextField!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+
+ let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
+ view.addGestureRecognizer(gestureRecognizer)
+ gestureRecognizer.cancelsTouchesInView = false
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+
+ dismissKeyboard()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func dismissKeyboard() {
+
+ view.endEditing(true)
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionRegister(_ sender: Any) {
+
+ let email = (fieldEmail.text ?? "").lowercased()
+ let password = fieldPassword.text ?? ""
+
+ if (email.count == 0) { ProgressHUD.showError("Please enter your email."); return }
+ if (password.count == 0) { ProgressHUD.showError("Please enter your password."); return }
+
+ LogoutUser(delAccount: DEL_ACCOUNT_NONE)
+
+ ProgressHUD.show(nil, interaction: false)
+
+ FUser.createUser(email: email, password: password) { user, error in
+ if (error == nil) {
+ Account.add(email: email, password: password)
+ self.dismiss(animated: true) {
+ self.delegate?.didRegisterUser()
+ }
+ } else {
+ ProgressHUD.showError(error!.localizedDescription)
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionDismiss(_ sender: Any) {
+
+ dismiss(animated: true)
+ }
+
+ // MARK: - UITextField delegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+
+ if (textField == fieldEmail) {
+ fieldPassword.becomeFirstResponder()
+ }
+ if (textField == fieldPassword) {
+ actionRegister(0)
+ }
+ return true
+ }
+}
diff --git a/Messenger/Classes/Welcome/RegisterEmail/RegisterEmailView.xib b/Messenger/Classes/Welcome/RegisterEmail/RegisterEmailView.xib
new file mode 100644
index 00000000..bae91b41
--- /dev/null
+++ b/Messenger/Classes/Welcome/RegisterEmail/RegisterEmailView.xib
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Classes/Welcome/WelcomeView.swift b/Messenger/Classes/Welcome/WelcomeView.swift
new file mode 100644
index 00000000..9929348e
--- /dev/null
+++ b/Messenger/Classes/Welcome/WelcomeView.swift
@@ -0,0 +1,78 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class WelcomeView: UIViewController, LoginGoogleDelegate, LoginPhoneDelegate, LoginEmailDelegate, RegisterEmailDelegate {
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+ }
+
+ // MARK: - Phone login methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionLoginPhone(_ sender: Any) {
+
+ AdvertPremium(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func didLoginPhone() {
+
+ }
+
+ // MARK: - Google login methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionLoginGoogle(_ sender: Any) {
+
+ AdvertPremium(target: self);
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func didLoginGoogle() {
+
+ }
+
+ // MARK: - Email login methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionLoginEmail(_ sender: Any) {
+
+ let loginEmailView = LoginEmailView()
+ loginEmailView.delegate = self
+ present(loginEmailView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func didLoginEmail() {
+
+ dismiss(animated: true) {
+ UserLoggedIn(loginMethod: LOGIN_EMAIL)
+ }
+ }
+
+ // MARK: - Email register methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionRegisterEmail(_ sender: Any) {
+
+ let registerEmailView = RegisterEmailView()
+ registerEmailView.delegate = self
+ present(registerEmailView, animated: true)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func didRegisterUser() {
+
+ dismiss(animated: true) {
+ UserLoggedIn(loginMethod: LOGIN_EMAIL)
+ }
+ }
+}
diff --git a/Messenger/Classes/Welcome/WelcomeView.xib b/Messenger/Classes/Welcome/WelcomeView.xib
new file mode 100644
index 00000000..aa61931a
--- /dev/null
+++ b/Messenger/Classes/Welcome/WelcomeView.xib
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Podfile b/Messenger/Podfile
new file mode 100644
index 00000000..6ab5ea76
--- /dev/null
+++ b/Messenger/Podfile
@@ -0,0 +1,27 @@
+platform :ios, '11.0'
+
+use_frameworks!
+
+inhibit_all_warnings!
+
+target 'app' do
+ pod 'ApiAI/Core'
+ pod 'Crashlytics'
+ pod 'Firebase/Core'
+ pod 'Firebase/Auth'
+ pod 'Firebase/Database'
+ pod 'Firebase/Storage'
+ pod 'GoogleSignIn'
+ pod 'OneSignal', '2.5.0'
+ pod 'Realm'
+ pod 'SinchRTC'
+ pod 'SinchVerification'
+
+ pod 'MBProgressHUD'
+ pod 'MGSwipeTableCell'
+ pod 'NYTPhotoViewer', '1.2.0'
+ pod 'ProgressHUD'
+ pod 'Reachability'
+ pod 'RNCryptor-objc'
+ pod 'SoundManager'
+end
diff --git a/Messenger/Resources/Images/addaccount/addaccount_logo@2x.png b/Messenger/Resources/Images/addaccount/addaccount_logo@2x.png
new file mode 100644
index 00000000..12170b2b
Binary files /dev/null and b/Messenger/Resources/Images/addaccount/addaccount_logo@2x.png differ
diff --git a/Messenger/Resources/Images/addfriends/addfriends_blank@2x.png b/Messenger/Resources/Images/addfriends/addfriends_blank@2x.png
new file mode 100644
index 00000000..7cfd2fa9
Binary files /dev/null and b/Messenger/Resources/Images/addfriends/addfriends_blank@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert01@2x.png b/Messenger/Resources/Images/advert/advert01@2x.png
new file mode 100755
index 00000000..ab1e58d6
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert01@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert02@2x.png b/Messenger/Resources/Images/advert/advert02@2x.png
new file mode 100755
index 00000000..1f741a1c
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert02@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert03@2x.png b/Messenger/Resources/Images/advert/advert03@2x.png
new file mode 100755
index 00000000..99fe18e1
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert03@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert04@2x.png b/Messenger/Resources/Images/advert/advert04@2x.png
new file mode 100755
index 00000000..b8819a99
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert04@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert05@2x.png b/Messenger/Resources/Images/advert/advert05@2x.png
new file mode 100755
index 00000000..31075556
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert05@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert06@2x.png b/Messenger/Resources/Images/advert/advert06@2x.png
new file mode 100755
index 00000000..b2d0bfc2
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert06@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert07@2x.png b/Messenger/Resources/Images/advert/advert07@2x.png
new file mode 100755
index 00000000..01a3d3dd
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert07@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert08@2x.png b/Messenger/Resources/Images/advert/advert08@2x.png
new file mode 100755
index 00000000..88a6eaa5
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert08@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert09@2x.png b/Messenger/Resources/Images/advert/advert09@2x.png
new file mode 100755
index 00000000..853f089b
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert09@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert10@2x.png b/Messenger/Resources/Images/advert/advert10@2x.png
new file mode 100755
index 00000000..336353ab
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert10@2x.png differ
diff --git a/Messenger/Resources/Images/advert/advert11@2x.png b/Messenger/Resources/Images/advert/advert11@2x.png
new file mode 100755
index 00000000..ffa2716d
Binary files /dev/null and b/Messenger/Resources/Images/advert/advert11@2x.png differ
diff --git a/Messenger/Resources/Images/allmedia/allmedia_blank@2x.png b/Messenger/Resources/Images/allmedia/allmedia_blank@2x.png
new file mode 100644
index 00000000..7aab5f71
Binary files /dev/null and b/Messenger/Resources/Images/allmedia/allmedia_blank@2x.png differ
diff --git a/Messenger/Resources/Images/allmedia/allmedia_selected@2x.png b/Messenger/Resources/Images/allmedia/allmedia_selected@2x.png
new file mode 100644
index 00000000..f88274ad
Binary files /dev/null and b/Messenger/Resources/Images/allmedia/allmedia_selected@2x.png differ
diff --git a/Messenger/Resources/Images/allmedia/allmedia_video@2x.png b/Messenger/Resources/Images/allmedia/allmedia_video@2x.png
new file mode 100644
index 00000000..a8e70e6a
Binary files /dev/null and b/Messenger/Resources/Images/allmedia/allmedia_video@2x.png differ
diff --git a/Messenger/Resources/Images/archive/archive_blank@2x.png b/Messenger/Resources/Images/archive/archive_blank@2x.png
new file mode 100644
index 00000000..4298f67f
Binary files /dev/null and b/Messenger/Resources/Images/archive/archive_blank@2x.png differ
diff --git a/Messenger/Resources/Images/archive/archive_muted@2x.png b/Messenger/Resources/Images/archive/archive_muted@2x.png
new file mode 100644
index 00000000..a1ff9711
Binary files /dev/null and b/Messenger/Resources/Images/archive/archive_muted@2x.png differ
diff --git a/Messenger/Resources/Images/blocked/blocked_blank@2x.png b/Messenger/Resources/Images/blocked/blocked_blank@2x.png
new file mode 100644
index 00000000..7cfd2fa9
Binary files /dev/null and b/Messenger/Resources/Images/blocked/blocked_blank@2x.png differ
diff --git a/Messenger/Resources/Images/callaudio/callaudio_answer@2x.png b/Messenger/Resources/Images/callaudio/callaudio_answer@2x.png
new file mode 100644
index 00000000..8950fb18
Binary files /dev/null and b/Messenger/Resources/Images/callaudio/callaudio_answer@2x.png differ
diff --git a/Messenger/Resources/Images/callaudio/callaudio_blank@2x.png b/Messenger/Resources/Images/callaudio/callaudio_blank@2x.png
new file mode 100644
index 00000000..54fb100c
Binary files /dev/null and b/Messenger/Resources/Images/callaudio/callaudio_blank@2x.png differ
diff --git a/Messenger/Resources/Images/callaudio/callaudio_hangup@2x.png b/Messenger/Resources/Images/callaudio/callaudio_hangup@2x.png
new file mode 100644
index 00000000..b1bbe7ce
Binary files /dev/null and b/Messenger/Resources/Images/callaudio/callaudio_hangup@2x.png differ
diff --git a/Messenger/Resources/Images/callaudio/callaudio_mute1@2x.png b/Messenger/Resources/Images/callaudio/callaudio_mute1@2x.png
new file mode 100644
index 00000000..145dec16
Binary files /dev/null and b/Messenger/Resources/Images/callaudio/callaudio_mute1@2x.png differ
diff --git a/Messenger/Resources/Images/callaudio/callaudio_mute2@2x.png b/Messenger/Resources/Images/callaudio/callaudio_mute2@2x.png
new file mode 100644
index 00000000..a2bd7403
Binary files /dev/null and b/Messenger/Resources/Images/callaudio/callaudio_mute2@2x.png differ
diff --git a/Messenger/Resources/Images/callaudio/callaudio_speaker1@2x.png b/Messenger/Resources/Images/callaudio/callaudio_speaker1@2x.png
new file mode 100644
index 00000000..69e0bf78
Binary files /dev/null and b/Messenger/Resources/Images/callaudio/callaudio_speaker1@2x.png differ
diff --git a/Messenger/Resources/Images/callaudio/callaudio_speaker2@2x.png b/Messenger/Resources/Images/callaudio/callaudio_speaker2@2x.png
new file mode 100644
index 00000000..301af262
Binary files /dev/null and b/Messenger/Resources/Images/callaudio/callaudio_speaker2@2x.png differ
diff --git a/Messenger/Resources/Images/callaudio/callaudio_video1@2x.png b/Messenger/Resources/Images/callaudio/callaudio_video1@2x.png
new file mode 100644
index 00000000..7a4c92b2
Binary files /dev/null and b/Messenger/Resources/Images/callaudio/callaudio_video1@2x.png differ
diff --git a/Messenger/Resources/Images/callaudio/callaudio_video2@2x.png b/Messenger/Resources/Images/callaudio/callaudio_video2@2x.png
new file mode 100644
index 00000000..7a4c92b2
Binary files /dev/null and b/Messenger/Resources/Images/callaudio/callaudio_video2@2x.png differ
diff --git a/Messenger/Resources/Images/callvideo/callvideo_answer@2x.png b/Messenger/Resources/Images/callvideo/callvideo_answer@2x.png
new file mode 100644
index 00000000..361dd9f2
Binary files /dev/null and b/Messenger/Resources/Images/callvideo/callvideo_answer@2x.png differ
diff --git a/Messenger/Resources/Images/callvideo/callvideo_blank@2x.png b/Messenger/Resources/Images/callvideo/callvideo_blank@2x.png
new file mode 100644
index 00000000..54fb100c
Binary files /dev/null and b/Messenger/Resources/Images/callvideo/callvideo_blank@2x.png differ
diff --git a/Messenger/Resources/Images/callvideo/callvideo_hangup@2x.png b/Messenger/Resources/Images/callvideo/callvideo_hangup@2x.png
new file mode 100644
index 00000000..ad6e76b2
Binary files /dev/null and b/Messenger/Resources/Images/callvideo/callvideo_hangup@2x.png differ
diff --git a/Messenger/Resources/Images/callvideo/callvideo_mute1@2x.png b/Messenger/Resources/Images/callvideo/callvideo_mute1@2x.png
new file mode 100644
index 00000000..ea6343bd
Binary files /dev/null and b/Messenger/Resources/Images/callvideo/callvideo_mute1@2x.png differ
diff --git a/Messenger/Resources/Images/callvideo/callvideo_mute2@2x.png b/Messenger/Resources/Images/callvideo/callvideo_mute2@2x.png
new file mode 100644
index 00000000..cea08a26
Binary files /dev/null and b/Messenger/Resources/Images/callvideo/callvideo_mute2@2x.png differ
diff --git a/Messenger/Resources/Images/callvideo/callvideo_switch1@2x.png b/Messenger/Resources/Images/callvideo/callvideo_switch1@2x.png
new file mode 100644
index 00000000..eaad56c6
Binary files /dev/null and b/Messenger/Resources/Images/callvideo/callvideo_switch1@2x.png differ
diff --git a/Messenger/Resources/Images/callvideo/callvideo_switch2@2x.png b/Messenger/Resources/Images/callvideo/callvideo_switch2@2x.png
new file mode 100644
index 00000000..acb03e67
Binary files /dev/null and b/Messenger/Resources/Images/callvideo/callvideo_switch2@2x.png differ
diff --git a/Messenger/Resources/Images/chat/chat_audio@2x.png b/Messenger/Resources/Images/chat/chat_audio@2x.png
new file mode 100644
index 00000000..f5c7ba68
Binary files /dev/null and b/Messenger/Resources/Images/chat/chat_audio@2x.png differ
diff --git a/Messenger/Resources/Images/chat/chat_back@2x.png b/Messenger/Resources/Images/chat/chat_back@2x.png
new file mode 100644
index 00000000..7d5d5838
Binary files /dev/null and b/Messenger/Resources/Images/chat/chat_back@2x.png differ
diff --git a/Messenger/Resources/Images/chat/chat_callaudio@2x.png b/Messenger/Resources/Images/chat/chat_callaudio@2x.png
new file mode 100644
index 00000000..674c3921
Binary files /dev/null and b/Messenger/Resources/Images/chat/chat_callaudio@2x.png differ
diff --git a/Messenger/Resources/Images/chat/chat_callvideo@2x.png b/Messenger/Resources/Images/chat/chat_callvideo@2x.png
new file mode 100644
index 00000000..16275b13
Binary files /dev/null and b/Messenger/Resources/Images/chat/chat_callvideo@2x.png differ
diff --git a/Messenger/Resources/Images/chat/chat_camera@2x.png b/Messenger/Resources/Images/chat/chat_camera@2x.png
new file mode 100644
index 00000000..bee99564
Binary files /dev/null and b/Messenger/Resources/Images/chat/chat_camera@2x.png differ
diff --git a/Messenger/Resources/Images/chat/chat_location@2x.png b/Messenger/Resources/Images/chat/chat_location@2x.png
new file mode 100644
index 00000000..7b3828eb
Binary files /dev/null and b/Messenger/Resources/Images/chat/chat_location@2x.png differ
diff --git a/Messenger/Resources/Images/chat/chat_picture@2x.png b/Messenger/Resources/Images/chat/chat_picture@2x.png
new file mode 100644
index 00000000..40666d66
Binary files /dev/null and b/Messenger/Resources/Images/chat/chat_picture@2x.png differ
diff --git a/Messenger/Resources/Images/chat/chat_sticker@2x.png b/Messenger/Resources/Images/chat/chat_sticker@2x.png
new file mode 100644
index 00000000..a985aa90
Binary files /dev/null and b/Messenger/Resources/Images/chat/chat_sticker@2x.png differ
diff --git a/Messenger/Resources/Images/chat/chat_video@2x.png b/Messenger/Resources/Images/chat/chat_video@2x.png
new file mode 100644
index 00000000..a6082cbd
Binary files /dev/null and b/Messenger/Resources/Images/chat/chat_video@2x.png differ
diff --git a/Messenger/Resources/Images/chats/chats_blank@2x.png b/Messenger/Resources/Images/chats/chats_blank@2x.png
new file mode 100644
index 00000000..4298f67f
Binary files /dev/null and b/Messenger/Resources/Images/chats/chats_blank@2x.png differ
diff --git a/Messenger/Resources/Images/chats/chats_dialogflow@2x.png b/Messenger/Resources/Images/chats/chats_dialogflow@2x.png
new file mode 100644
index 00000000..1e5d6acc
Binary files /dev/null and b/Messenger/Resources/Images/chats/chats_dialogflow@2x.png differ
diff --git a/Messenger/Resources/Images/chats/chats_muted@2x.png b/Messenger/Resources/Images/chats/chats_muted@2x.png
new file mode 100644
index 00000000..a1ff9711
Binary files /dev/null and b/Messenger/Resources/Images/chats/chats_muted@2x.png differ
diff --git a/Messenger/Resources/Images/creategroup/creategroup_blank@2x.png b/Messenger/Resources/Images/creategroup/creategroup_blank@2x.png
new file mode 100755
index 00000000..d5a9a453
Binary files /dev/null and b/Messenger/Resources/Images/creategroup/creategroup_blank@2x.png differ
diff --git a/Messenger/Resources/Images/editprofile/editprofile_blank@2x.png b/Messenger/Resources/Images/editprofile/editprofile_blank@2x.png
new file mode 100644
index 00000000..54fb100c
Binary files /dev/null and b/Messenger/Resources/Images/editprofile/editprofile_blank@2x.png differ
diff --git a/Messenger/Resources/Images/group/group_blank@2x.png b/Messenger/Resources/Images/group/group_blank@2x.png
new file mode 100755
index 00000000..d5a9a453
Binary files /dev/null and b/Messenger/Resources/Images/group/group_blank@2x.png differ
diff --git a/Messenger/Resources/Images/group/group_more@2x.png b/Messenger/Resources/Images/group/group_more@2x.png
new file mode 100755
index 00000000..13846cae
Binary files /dev/null and b/Messenger/Resources/Images/group/group_more@2x.png differ
diff --git a/Messenger/Resources/Images/groups/groups_blank@2x.png b/Messenger/Resources/Images/groups/groups_blank@2x.png
new file mode 100755
index 00000000..b1623891
Binary files /dev/null and b/Messenger/Resources/Images/groups/groups_blank@2x.png differ
diff --git a/Messenger/Resources/Images/launchscreen/launchscreen_logo@2x.png b/Messenger/Resources/Images/launchscreen/launchscreen_logo@2x.png
new file mode 100644
index 00000000..12170b2b
Binary files /dev/null and b/Messenger/Resources/Images/launchscreen/launchscreen_logo@2x.png differ
diff --git a/Messenger/Resources/Images/login/login_logo@2x.png b/Messenger/Resources/Images/login/login_logo@2x.png
new file mode 100644
index 00000000..73bb9c1c
Binary files /dev/null and b/Messenger/Resources/Images/login/login_logo@2x.png differ
diff --git a/Messenger/Resources/Images/people/people_blank@2x.png b/Messenger/Resources/Images/people/people_blank@2x.png
new file mode 100644
index 00000000..7cfd2fa9
Binary files /dev/null and b/Messenger/Resources/Images/people/people_blank@2x.png differ
diff --git a/Messenger/Resources/Images/profile/profile_blank@2x.png b/Messenger/Resources/Images/profile/profile_blank@2x.png
new file mode 100644
index 00000000..54fb100c
Binary files /dev/null and b/Messenger/Resources/Images/profile/profile_blank@2x.png differ
diff --git a/Messenger/Resources/Images/profile/profile_callaudio@2x.png b/Messenger/Resources/Images/profile/profile_callaudio@2x.png
new file mode 100644
index 00000000..08cacf4a
Binary files /dev/null and b/Messenger/Resources/Images/profile/profile_callaudio@2x.png differ
diff --git a/Messenger/Resources/Images/profile/profile_callvideo@2x.png b/Messenger/Resources/Images/profile/profile_callvideo@2x.png
new file mode 100644
index 00000000..3f156947
Binary files /dev/null and b/Messenger/Resources/Images/profile/profile_callvideo@2x.png differ
diff --git a/Messenger/Resources/Images/register/register_logo@2x.png b/Messenger/Resources/Images/register/register_logo@2x.png
new file mode 100644
index 00000000..73bb9c1c
Binary files /dev/null and b/Messenger/Resources/Images/register/register_logo@2x.png differ
diff --git a/Messenger/Resources/Images/selectuser/selectuser_blank@2x.png b/Messenger/Resources/Images/selectuser/selectuser_blank@2x.png
new file mode 100644
index 00000000..7cfd2fa9
Binary files /dev/null and b/Messenger/Resources/Images/selectuser/selectuser_blank@2x.png differ
diff --git a/Messenger/Resources/Images/selectusers/selectusers_blank@2x.png b/Messenger/Resources/Images/selectusers/selectusers_blank@2x.png
new file mode 100644
index 00000000..7cfd2fa9
Binary files /dev/null and b/Messenger/Resources/Images/selectusers/selectusers_blank@2x.png differ
diff --git a/Messenger/Resources/Images/settings/settings_blank@2x.png b/Messenger/Resources/Images/settings/settings_blank@2x.png
new file mode 100644
index 00000000..54fb100c
Binary files /dev/null and b/Messenger/Resources/Images/settings/settings_blank@2x.png differ
diff --git a/Messenger/Resources/Images/stickers/stickers_blank@2x.png b/Messenger/Resources/Images/stickers/stickers_blank@2x.png
new file mode 100644
index 00000000..9ff4049c
Binary files /dev/null and b/Messenger/Resources/Images/stickers/stickers_blank@2x.png differ
diff --git a/Messenger/Resources/Images/switchaccount/switchaccount_blank@2x.png b/Messenger/Resources/Images/switchaccount/switchaccount_blank@2x.png
new file mode 100644
index 00000000..4298f67f
Binary files /dev/null and b/Messenger/Resources/Images/switchaccount/switchaccount_blank@2x.png differ
diff --git a/Messenger/Resources/Images/tabbar/tab_calls@2x.png b/Messenger/Resources/Images/tabbar/tab_calls@2x.png
new file mode 100644
index 00000000..1dde387d
Binary files /dev/null and b/Messenger/Resources/Images/tabbar/tab_calls@2x.png differ
diff --git a/Messenger/Resources/Images/tabbar/tab_chats@2x.png b/Messenger/Resources/Images/tabbar/tab_chats@2x.png
new file mode 100755
index 00000000..1a267c09
Binary files /dev/null and b/Messenger/Resources/Images/tabbar/tab_chats@2x.png differ
diff --git a/Messenger/Resources/Images/tabbar/tab_groups@2x.png b/Messenger/Resources/Images/tabbar/tab_groups@2x.png
new file mode 100755
index 00000000..1bacf8af
Binary files /dev/null and b/Messenger/Resources/Images/tabbar/tab_groups@2x.png differ
diff --git a/Messenger/Resources/Images/tabbar/tab_people@2x.png b/Messenger/Resources/Images/tabbar/tab_people@2x.png
new file mode 100755
index 00000000..ef655933
Binary files /dev/null and b/Messenger/Resources/Images/tabbar/tab_people@2x.png differ
diff --git a/Messenger/Resources/Images/tabbar/tab_settings@2x.png b/Messenger/Resources/Images/tabbar/tab_settings@2x.png
new file mode 100755
index 00000000..d18482aa
Binary files /dev/null and b/Messenger/Resources/Images/tabbar/tab_settings@2x.png differ
diff --git a/Messenger/Resources/Images/wallpaper/wallpaper_selected@2x.png b/Messenger/Resources/Images/wallpaper/wallpaper_selected@2x.png
new file mode 100644
index 00000000..7aec18f4
Binary files /dev/null and b/Messenger/Resources/Images/wallpaper/wallpaper_selected@2x.png differ
diff --git a/Messenger/Resources/Images/welcome/welcome_logo@2x.png b/Messenger/Resources/Images/welcome/welcome_logo@2x.png
new file mode 100644
index 00000000..12170b2b
Binary files /dev/null and b/Messenger/Resources/Images/welcome/welcome_logo@2x.png differ
diff --git a/Messenger/Resources/Plists/countries.plist b/Messenger/Resources/Plists/countries.plist
new file mode 100755
index 00000000..793e9c4c
--- /dev/null
+++ b/Messenger/Resources/Plists/countries.plist
@@ -0,0 +1,1934 @@
+
+
+
+
+
+ code
+ AF
+ dial_code
+ +93
+ name
+ Afghanistan
+
+
+ code
+ AL
+ dial_code
+ +355
+ name
+ Albania
+
+
+ code
+ DZ
+ dial_code
+ +213
+ name
+ Algeria
+
+
+ code
+ AS
+ dial_code
+ +1 684
+ name
+ AmericanSamoa
+
+
+ code
+ AD
+ dial_code
+ +376
+ name
+ Andorra
+
+
+ code
+ AO
+ dial_code
+ +244
+ name
+ Angola
+
+
+ code
+ AI
+ dial_code
+ +1 264
+ name
+ Anguilla
+
+
+ code
+ AG
+ dial_code
+ +1268
+ name
+ Antigua and Barbuda
+
+
+ code
+ AR
+ dial_code
+ +54
+ name
+ Argentina
+
+
+ code
+ AM
+ dial_code
+ +374
+ name
+ Armenia
+
+
+ code
+ AW
+ dial_code
+ +297
+ name
+ Aruba
+
+
+ code
+ AU
+ dial_code
+ +61
+ name
+ Australia
+
+
+ code
+ AT
+ dial_code
+ +43
+ name
+ Austria
+
+
+ code
+ AZ
+ dial_code
+ +994
+ name
+ Azerbaijan
+
+
+ code
+ BS
+ dial_code
+ +1 242
+ name
+ Bahamas
+
+
+ code
+ BH
+ dial_code
+ +973
+ name
+ Bahrain
+
+
+ code
+ BD
+ dial_code
+ +880
+ name
+ Bangladesh
+
+
+ code
+ BB
+ dial_code
+ +1 246
+ name
+ Barbados
+
+
+ code
+ BY
+ dial_code
+ +375
+ name
+ Belarus
+
+
+ code
+ BE
+ dial_code
+ +32
+ name
+ Belgium
+
+
+ code
+ BZ
+ dial_code
+ +501
+ name
+ Belize
+
+
+ code
+ BJ
+ dial_code
+ +229
+ name
+ Benin
+
+
+ code
+ BM
+ dial_code
+ +1 441
+ name
+ Bermuda
+
+
+ code
+ BT
+ dial_code
+ +975
+ name
+ Bhutan
+
+
+ code
+ BA
+ dial_code
+ +387
+ name
+ Bosnia and Herzegovina
+
+
+ code
+ BW
+ dial_code
+ +267
+ name
+ Botswana
+
+
+ code
+ BR
+ dial_code
+ +55
+ name
+ Brazil
+
+
+ code
+ IO
+ dial_code
+ +246
+ name
+ British Indian Ocean Territory
+
+
+ code
+ BG
+ dial_code
+ +359
+ name
+ Bulgaria
+
+
+ code
+ BF
+ dial_code
+ +226
+ name
+ Burkina Faso
+
+
+ code
+ BI
+ dial_code
+ +257
+ name
+ Burundi
+
+
+ code
+ KH
+ dial_code
+ +855
+ name
+ Cambodia
+
+
+ code
+ CM
+ dial_code
+ +237
+ name
+ Cameroon
+
+
+ code
+ CA
+ dial_code
+ +1
+ name
+ Canada
+
+
+ code
+ CV
+ dial_code
+ +238
+ name
+ Cape Verde
+
+
+ code
+ KY
+ dial_code
+ + 345
+ name
+ Cayman Islands
+
+
+ code
+ CF
+ dial_code
+ +236
+ name
+ Central African Republic
+
+
+ code
+ TD
+ dial_code
+ +235
+ name
+ Chad
+
+
+ code
+ CL
+ dial_code
+ +56
+ name
+ Chile
+
+
+ code
+ CN
+ dial_code
+ +86
+ name
+ China
+
+
+ code
+ CX
+ dial_code
+ +61
+ name
+ Christmas Island
+
+
+ code
+ CO
+ dial_code
+ +57
+ name
+ Colombia
+
+
+ code
+ KM
+ dial_code
+ +269
+ name
+ Comoros
+
+
+ code
+ CG
+ dial_code
+ +242
+ name
+ Congo
+
+
+ code
+ CK
+ dial_code
+ +682
+ name
+ Cook Islands
+
+
+ code
+ CR
+ dial_code
+ +506
+ name
+ Costa Rica
+
+
+ code
+ HR
+ dial_code
+ +385
+ name
+ Croatia
+
+
+ code
+ CU
+ dial_code
+ +53
+ name
+ Cuba
+
+
+ code
+ CY
+ dial_code
+ +537
+ name
+ Cyprus
+
+
+ code
+ CZ
+ dial_code
+ +420
+ name
+ Czech Republic
+
+
+ code
+ DK
+ dial_code
+ +45
+ name
+ Denmark
+
+
+ code
+ DJ
+ dial_code
+ +253
+ name
+ Djibouti
+
+
+ code
+ DM
+ dial_code
+ +1 767
+ name
+ Dominica
+
+
+ code
+ DO
+ dial_code
+ +1 849
+ name
+ Dominican Republic
+
+
+ code
+ EC
+ dial_code
+ +593
+ name
+ Ecuador
+
+
+ code
+ EG
+ dial_code
+ +20
+ name
+ Egypt
+
+
+ code
+ SV
+ dial_code
+ +503
+ name
+ El Salvador
+
+
+ code
+ GQ
+ dial_code
+ +240
+ name
+ Equatorial Guinea
+
+
+ code
+ ER
+ dial_code
+ +291
+ name
+ Eritrea
+
+
+ code
+ EE
+ dial_code
+ +372
+ name
+ Estonia
+
+
+ code
+ ET
+ dial_code
+ +251
+ name
+ Ethiopia
+
+
+ code
+ FO
+ dial_code
+ +298
+ name
+ Faroe Islands
+
+
+ code
+ FJ
+ dial_code
+ +679
+ name
+ Fiji
+
+
+ code
+ FI
+ dial_code
+ +358
+ name
+ Finland
+
+
+ code
+ FR
+ dial_code
+ +33
+ name
+ France
+
+
+ code
+ GF
+ dial_code
+ +594
+ name
+ French Guiana
+
+
+ code
+ PF
+ dial_code
+ +689
+ name
+ French Polynesia
+
+
+ code
+ GA
+ dial_code
+ +241
+ name
+ Gabon
+
+
+ code
+ GM
+ dial_code
+ +220
+ name
+ Gambia
+
+
+ code
+ GE
+ dial_code
+ +995
+ name
+ Georgia
+
+
+ code
+ DE
+ dial_code
+ +49
+ name
+ Germany
+
+
+ code
+ GH
+ dial_code
+ +233
+ name
+ Ghana
+
+
+ code
+ GI
+ dial_code
+ +350
+ name
+ Gibraltar
+
+
+ code
+ GR
+ dial_code
+ +30
+ name
+ Greece
+
+
+ code
+ GL
+ dial_code
+ +299
+ name
+ Greenland
+
+
+ code
+ GD
+ dial_code
+ +1 473
+ name
+ Grenada
+
+
+ code
+ GP
+ dial_code
+ +590
+ name
+ Guadeloupe
+
+
+ code
+ GU
+ dial_code
+ +1 671
+ name
+ Guam
+
+
+ code
+ GT
+ dial_code
+ +502
+ name
+ Guatemala
+
+
+ code
+ GN
+ dial_code
+ +224
+ name
+ Guinea
+
+
+ code
+ GW
+ dial_code
+ +245
+ name
+ Guinea-Bissau
+
+
+ code
+ GY
+ dial_code
+ +595
+ name
+ Guyana
+
+
+ code
+ HT
+ dial_code
+ +509
+ name
+ Haiti
+
+
+ code
+ HN
+ dial_code
+ +504
+ name
+ Honduras
+
+
+ code
+ HU
+ dial_code
+ +36
+ name
+ Hungary
+
+
+ code
+ IS
+ dial_code
+ +354
+ name
+ Iceland
+
+
+ code
+ IN
+ dial_code
+ +91
+ name
+ India
+
+
+ code
+ ID
+ dial_code
+ +62
+ name
+ Indonesia
+
+
+ code
+ IQ
+ dial_code
+ +964
+ name
+ Iraq
+
+
+ code
+ IE
+ dial_code
+ +353
+ name
+ Ireland
+
+
+ code
+ IL
+ dial_code
+ +972
+ name
+ Israel
+
+
+ code
+ IT
+ dial_code
+ +39
+ name
+ Italy
+
+
+ code
+ JM
+ dial_code
+ +1 876
+ name
+ Jamaica
+
+
+ code
+ JP
+ dial_code
+ +81
+ name
+ Japan
+
+
+ code
+ JO
+ dial_code
+ +962
+ name
+ Jordan
+
+
+ code
+ KZ
+ dial_code
+ +7 7
+ name
+ Kazakhstan
+
+
+ code
+ KE
+ dial_code
+ +254
+ name
+ Kenya
+
+
+ code
+ KI
+ dial_code
+ +686
+ name
+ Kiribati
+
+
+ code
+ KW
+ dial_code
+ +965
+ name
+ Kuwait
+
+
+ code
+ KG
+ dial_code
+ +996
+ name
+ Kyrgyzstan
+
+
+ code
+ LV
+ dial_code
+ +371
+ name
+ Latvia
+
+
+ code
+ LB
+ dial_code
+ +961
+ name
+ Lebanon
+
+
+ code
+ LS
+ dial_code
+ +266
+ name
+ Lesotho
+
+
+ code
+ LR
+ dial_code
+ +231
+ name
+ Liberia
+
+
+ code
+ LI
+ dial_code
+ +423
+ name
+ Liechtenstein
+
+
+ code
+ LT
+ dial_code
+ +370
+ name
+ Lithuania
+
+
+ code
+ LU
+ dial_code
+ +352
+ name
+ Luxembourg
+
+
+ code
+ MG
+ dial_code
+ +261
+ name
+ Madagascar
+
+
+ code
+ MW
+ dial_code
+ +265
+ name
+ Malawi
+
+
+ code
+ MY
+ dial_code
+ +60
+ name
+ Malaysia
+
+
+ code
+ MV
+ dial_code
+ +960
+ name
+ Maldives
+
+
+ code
+ ML
+ dial_code
+ +223
+ name
+ Mali
+
+
+ code
+ MT
+ dial_code
+ +356
+ name
+ Malta
+
+
+ code
+ MH
+ dial_code
+ +692
+ name
+ Marshall Islands
+
+
+ code
+ MQ
+ dial_code
+ +596
+ name
+ Martinique
+
+
+ code
+ MR
+ dial_code
+ +222
+ name
+ Mauritania
+
+
+ code
+ MU
+ dial_code
+ +230
+ name
+ Mauritius
+
+
+ code
+ YT
+ dial_code
+ +262
+ name
+ Mayotte
+
+
+ code
+ MX
+ dial_code
+ +52
+ name
+ Mexico
+
+
+ code
+ MC
+ dial_code
+ +377
+ name
+ Monaco
+
+
+ code
+ MN
+ dial_code
+ +976
+ name
+ Mongolia
+
+
+ code
+ ME
+ dial_code
+ +382
+ name
+ Montenegro
+
+
+ code
+ MS
+ dial_code
+ +1664
+ name
+ Montserrat
+
+
+ code
+ MA
+ dial_code
+ +212
+ name
+ Morocco
+
+
+ code
+ MM
+ dial_code
+ +95
+ name
+ Myanmar
+
+
+ code
+ NA
+ dial_code
+ +264
+ name
+ Namibia
+
+
+ code
+ NR
+ dial_code
+ +674
+ name
+ Nauru
+
+
+ code
+ NP
+ dial_code
+ +977
+ name
+ Nepal
+
+
+ code
+ NL
+ dial_code
+ +31
+ name
+ Netherlands
+
+
+ code
+ AN
+ dial_code
+ +599
+ name
+ Netherlands Antilles
+
+
+ code
+ NC
+ dial_code
+ +687
+ name
+ New Caledonia
+
+
+ code
+ NZ
+ dial_code
+ +64
+ name
+ New Zealand
+
+
+ code
+ NI
+ dial_code
+ +505
+ name
+ Nicaragua
+
+
+ code
+ NE
+ dial_code
+ +227
+ name
+ Niger
+
+
+ code
+ NG
+ dial_code
+ +234
+ name
+ Nigeria
+
+
+ code
+ NU
+ dial_code
+ +683
+ name
+ Niue
+
+
+ code
+ NF
+ dial_code
+ +672
+ name
+ Norfolk Island
+
+
+ code
+ MP
+ dial_code
+ +1 670
+ name
+ Northern Mariana Islands
+
+
+ code
+ NO
+ dial_code
+ +47
+ name
+ Norway
+
+
+ code
+ OM
+ dial_code
+ +968
+ name
+ Oman
+
+
+ code
+ PK
+ dial_code
+ +92
+ name
+ Pakistan
+
+
+ code
+ PW
+ dial_code
+ +680
+ name
+ Palau
+
+
+ code
+ PA
+ dial_code
+ +507
+ name
+ Panama
+
+
+ code
+ PG
+ dial_code
+ +675
+ name
+ Papua New Guinea
+
+
+ code
+ PY
+ dial_code
+ +595
+ name
+ Paraguay
+
+
+ code
+ PE
+ dial_code
+ +51
+ name
+ Peru
+
+
+ code
+ PH
+ dial_code
+ +63
+ name
+ Philippines
+
+
+ code
+ PL
+ dial_code
+ +48
+ name
+ Poland
+
+
+ code
+ PT
+ dial_code
+ +351
+ name
+ Portugal
+
+
+ code
+ PR
+ dial_code
+ +1 939
+ name
+ Puerto Rico
+
+
+ code
+ QA
+ dial_code
+ +974
+ name
+ Qatar
+
+
+ code
+ RO
+ dial_code
+ +40
+ name
+ Romania
+
+
+ code
+ RW
+ dial_code
+ +250
+ name
+ Rwanda
+
+
+ code
+ WS
+ dial_code
+ +685
+ name
+ Samoa
+
+
+ code
+ SM
+ dial_code
+ +378
+ name
+ San Marino
+
+
+ code
+ SA
+ dial_code
+ +966
+ name
+ Saudi Arabia
+
+
+ code
+ SN
+ dial_code
+ +221
+ name
+ Senegal
+
+
+ code
+ RS
+ dial_code
+ +381
+ name
+ Serbia
+
+
+ code
+ SC
+ dial_code
+ +248
+ name
+ Seychelles
+
+
+ code
+ SL
+ dial_code
+ +232
+ name
+ Sierra Leone
+
+
+ code
+ SG
+ dial_code
+ +65
+ name
+ Singapore
+
+
+ code
+ SK
+ dial_code
+ +421
+ name
+ Slovakia
+
+
+ code
+ SI
+ dial_code
+ +386
+ name
+ Slovenia
+
+
+ code
+ SB
+ dial_code
+ +677
+ name
+ Solomon Islands
+
+
+ code
+ ZA
+ dial_code
+ +27
+ name
+ South Africa
+
+
+ code
+ GS
+ dial_code
+ +500
+ name
+ South Georgia and the South Sandwich Islands
+
+
+ code
+ ES
+ dial_code
+ +34
+ name
+ Spain
+
+
+ code
+ LK
+ dial_code
+ +94
+ name
+ Sri Lanka
+
+
+ code
+ SD
+ dial_code
+ +249
+ name
+ Sudan
+
+
+ code
+ SR
+ dial_code
+ +597
+ name
+ Suriname
+
+
+ code
+ SZ
+ dial_code
+ +268
+ name
+ Swaziland
+
+
+ code
+ SE
+ dial_code
+ +46
+ name
+ Sweden
+
+
+ code
+ CH
+ dial_code
+ +41
+ name
+ Switzerland
+
+
+ code
+ TJ
+ dial_code
+ +992
+ name
+ Tajikistan
+
+
+ code
+ TH
+ dial_code
+ +66
+ name
+ Thailand
+
+
+ code
+ TG
+ dial_code
+ +228
+ name
+ Togo
+
+
+ code
+ TK
+ dial_code
+ +690
+ name
+ Tokelau
+
+
+ code
+ TO
+ dial_code
+ +676
+ name
+ Tonga
+
+
+ code
+ TT
+ dial_code
+ +1 868
+ name
+ Trinidad and Tobago
+
+
+ code
+ TN
+ dial_code
+ +216
+ name
+ Tunisia
+
+
+ code
+ TR
+ dial_code
+ +90
+ name
+ Turkey
+
+
+ code
+ TM
+ dial_code
+ +993
+ name
+ Turkmenistan
+
+
+ code
+ TC
+ dial_code
+ +1 649
+ name
+ Turks and Caicos Islands
+
+
+ code
+ TV
+ dial_code
+ +688
+ name
+ Tuvalu
+
+
+ code
+ UG
+ dial_code
+ +256
+ name
+ Uganda
+
+
+ code
+ UA
+ dial_code
+ +380
+ name
+ Ukraine
+
+
+ code
+ AE
+ dial_code
+ +971
+ name
+ United Arab Emirates
+
+
+ code
+ GB
+ dial_code
+ +44
+ name
+ United Kingdom
+
+
+ code
+ US
+ dial_code
+ +1
+ name
+ United States
+
+
+ code
+ UY
+ dial_code
+ +598
+ name
+ Uruguay
+
+
+ code
+ UZ
+ dial_code
+ +998
+ name
+ Uzbekistan
+
+
+ code
+ VU
+ dial_code
+ +678
+ name
+ Vanuatu
+
+
+ code
+ WF
+ dial_code
+ +681
+ name
+ Wallis and Futuna
+
+
+ code
+ YE
+ dial_code
+ +967
+ name
+ Yemen
+
+
+ code
+ ZM
+ dial_code
+ +260
+ name
+ Zambia
+
+
+ code
+ ZW
+ dial_code
+ +263
+ name
+ Zimbabwe
+
+
+ code
+ AX
+ dial_code
+ +358
+ name
+ Åland Islands
+
+
+ code
+ AQ
+ dial_code
+
+ name
+ Antarctica
+
+
+ code
+ BO
+ dial_code
+ +591
+ name
+ Bolivia, Plurinational State of
+
+
+ code
+ BN
+ dial_code
+ +673
+ name
+ Brunei Darussalam
+
+
+ code
+ CC
+ dial_code
+ +61
+ name
+ Cocos (Keeling) Islands
+
+
+ code
+ CD
+ dial_code
+ +243
+ name
+ Congo, The Democratic Republic of the
+
+
+ code
+ CI
+ dial_code
+ +225
+ name
+ Cote d'Ivoire
+
+
+ code
+ FK
+ dial_code
+ +500
+ name
+ Falkland Islands (Malvinas)
+
+
+ code
+ GG
+ dial_code
+ +44
+ name
+ Guernsey
+
+
+ code
+ VA
+ dial_code
+ +379
+ name
+ Holy See (Vatican City State)
+
+
+ code
+ HK
+ dial_code
+ +852
+ name
+ Hong Kong
+
+
+ code
+ IR
+ dial_code
+ +98
+ name
+ Iran, Islamic Republic of
+
+
+ code
+ IM
+ dial_code
+ +44
+ name
+ Isle of Man
+
+
+ code
+ JE
+ dial_code
+ +44
+ name
+ Jersey
+
+
+ code
+ KP
+ dial_code
+ +850
+ name
+ Korea, Democratic People's Republic of
+
+
+ code
+ KR
+ dial_code
+ +82
+ name
+ Korea, Republic of
+
+
+ code
+ LA
+ dial_code
+ +856
+ name
+ Lao People's Democratic Republic
+
+
+ code
+ LY
+ dial_code
+ +218
+ name
+ Libyan Arab Jamahiriya
+
+
+ code
+ MO
+ dial_code
+ +853
+ name
+ Macao
+
+
+ code
+ MK
+ dial_code
+ +389
+ name
+ Macedonia, The Former Yugoslav Republic of
+
+
+ code
+ FM
+ dial_code
+ +691
+ name
+ Micronesia, Federated States of
+
+
+ code
+ MD
+ dial_code
+ +373
+ name
+ Moldova, Republic of
+
+
+ code
+ MZ
+ dial_code
+ +258
+ name
+ Mozambique
+
+
+ code
+ PS
+ dial_code
+ +970
+ name
+ Palestinian Territory, Occupied
+
+
+ code
+ PN
+ dial_code
+ +872
+ name
+ Pitcairn
+
+
+ code
+ RE
+ dial_code
+ +262
+ name
+ Réunion
+
+
+ code
+ RU
+ dial_code
+ +7
+ name
+ Russia
+
+
+ code
+ BL
+ dial_code
+ +590
+ name
+ Saint Barthélemy
+
+
+ code
+ SH
+ dial_code
+ +290
+ name
+ Saint Helena, Ascension and Tristan Da Cunha
+
+
+ code
+ KN
+ dial_code
+ +1 869
+ name
+ Saint Kitts and Nevis
+
+
+ code
+ LC
+ dial_code
+ +1 758
+ name
+ Saint Lucia
+
+
+ code
+ MF
+ dial_code
+ +590
+ name
+ Saint Martin
+
+
+ code
+ PM
+ dial_code
+ +508
+ name
+ Saint Pierre and Miquelon
+
+
+ code
+ VC
+ dial_code
+ +1 784
+ name
+ Saint Vincent and the Grenadines
+
+
+ code
+ ST
+ dial_code
+ +239
+ name
+ Sao Tome and Principe
+
+
+ code
+ SO
+ dial_code
+ +252
+ name
+ Somalia
+
+
+ code
+ SJ
+ dial_code
+ +47
+ name
+ Svalbard and Jan Mayen
+
+
+ code
+ SY
+ dial_code
+ +963
+ name
+ Syrian Arab Republic
+
+
+ code
+ TW
+ dial_code
+ +886
+ name
+ Taiwan, Province of China
+
+
+ code
+ TZ
+ dial_code
+ +255
+ name
+ Tanzania, United Republic of
+
+
+ code
+ TL
+ dial_code
+ +670
+ name
+ Timor-Leste
+
+
+ code
+ VE
+ dial_code
+ +58
+ name
+ Venezuela, Bolivarian Republic of
+
+
+ code
+ VN
+ dial_code
+ +84
+ name
+ Viet Nam
+
+
+ code
+ VG
+ dial_code
+ +1 284
+ name
+ Virgin Islands, British
+
+
+ code
+ VI
+ dial_code
+ +1 340
+ name
+ Virgin Islands, U.S.
+
+
+
diff --git a/Messenger/Resources/Sounds/call/call_incoming.wav b/Messenger/Resources/Sounds/call/call_incoming.wav
new file mode 100644
index 00000000..679a0ec3
Binary files /dev/null and b/Messenger/Resources/Sounds/call/call_incoming.wav differ
diff --git a/Messenger/Resources/Sounds/call/call_ringback.wav b/Messenger/Resources/Sounds/call/call_ringback.wav
new file mode 100644
index 00000000..36058ad2
Binary files /dev/null and b/Messenger/Resources/Sounds/call/call_ringback.wav differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal01@2x.png b/Messenger/Resources/Stickers1/stickerlocal01@2x.png
new file mode 100755
index 00000000..80718fc3
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal01@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal02@2x.png b/Messenger/Resources/Stickers1/stickerlocal02@2x.png
new file mode 100755
index 00000000..5906d98c
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal02@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal03@2x.png b/Messenger/Resources/Stickers1/stickerlocal03@2x.png
new file mode 100755
index 00000000..a1fc52d9
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal03@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal04@2x.png b/Messenger/Resources/Stickers1/stickerlocal04@2x.png
new file mode 100755
index 00000000..7777b9db
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal04@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal05@2x.png b/Messenger/Resources/Stickers1/stickerlocal05@2x.png
new file mode 100755
index 00000000..8ce94f06
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal05@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal06@2x.png b/Messenger/Resources/Stickers1/stickerlocal06@2x.png
new file mode 100755
index 00000000..ab1e58d6
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal06@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal07@2x.png b/Messenger/Resources/Stickers1/stickerlocal07@2x.png
new file mode 100755
index 00000000..1f741a1c
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal07@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal08@2x.png b/Messenger/Resources/Stickers1/stickerlocal08@2x.png
new file mode 100755
index 00000000..10d2f774
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal08@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal09@2x.png b/Messenger/Resources/Stickers1/stickerlocal09@2x.png
new file mode 100755
index 00000000..612133bb
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal09@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal10@2x.png b/Messenger/Resources/Stickers1/stickerlocal10@2x.png
new file mode 100755
index 00000000..67d1b97c
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal10@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal11@2x.png b/Messenger/Resources/Stickers1/stickerlocal11@2x.png
new file mode 100755
index 00000000..26d7bf28
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal11@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal12@2x.png b/Messenger/Resources/Stickers1/stickerlocal12@2x.png
new file mode 100755
index 00000000..50d390d9
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal12@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal13@2x.png b/Messenger/Resources/Stickers1/stickerlocal13@2x.png
new file mode 100755
index 00000000..f35166d7
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal13@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal14@2x.png b/Messenger/Resources/Stickers1/stickerlocal14@2x.png
new file mode 100755
index 00000000..5d3db2a4
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal14@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal15@2x.png b/Messenger/Resources/Stickers1/stickerlocal15@2x.png
new file mode 100755
index 00000000..98b27615
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal15@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal16@2x.png b/Messenger/Resources/Stickers1/stickerlocal16@2x.png
new file mode 100755
index 00000000..99fe18e1
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal16@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal17@2x.png b/Messenger/Resources/Stickers1/stickerlocal17@2x.png
new file mode 100755
index 00000000..bebbcfa4
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal17@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal18@2x.png b/Messenger/Resources/Stickers1/stickerlocal18@2x.png
new file mode 100755
index 00000000..b8819a99
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal18@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal19@2x.png b/Messenger/Resources/Stickers1/stickerlocal19@2x.png
new file mode 100755
index 00000000..31075556
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal19@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal20@2x.png b/Messenger/Resources/Stickers1/stickerlocal20@2x.png
new file mode 100755
index 00000000..b2d0bfc2
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal20@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal21@2x.png b/Messenger/Resources/Stickers1/stickerlocal21@2x.png
new file mode 100755
index 00000000..ca98b43d
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal21@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal22@2x.png b/Messenger/Resources/Stickers1/stickerlocal22@2x.png
new file mode 100755
index 00000000..d0af7ef7
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal22@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal23@2x.png b/Messenger/Resources/Stickers1/stickerlocal23@2x.png
new file mode 100755
index 00000000..e7175762
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal23@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal24@2x.png b/Messenger/Resources/Stickers1/stickerlocal24@2x.png
new file mode 100755
index 00000000..5ffcb150
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal24@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal25@2x.png b/Messenger/Resources/Stickers1/stickerlocal25@2x.png
new file mode 100755
index 00000000..2d9ad449
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal25@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal26@2x.png b/Messenger/Resources/Stickers1/stickerlocal26@2x.png
new file mode 100755
index 00000000..835249da
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal26@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal27@2x.png b/Messenger/Resources/Stickers1/stickerlocal27@2x.png
new file mode 100755
index 00000000..3e4f8ca4
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal27@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal28@2x.png b/Messenger/Resources/Stickers1/stickerlocal28@2x.png
new file mode 100755
index 00000000..c98d5bc4
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal28@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal29@2x.png b/Messenger/Resources/Stickers1/stickerlocal29@2x.png
new file mode 100755
index 00000000..7961b8c4
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal29@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal30@2x.png b/Messenger/Resources/Stickers1/stickerlocal30@2x.png
new file mode 100755
index 00000000..07b976ce
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal30@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal31@2x.png b/Messenger/Resources/Stickers1/stickerlocal31@2x.png
new file mode 100755
index 00000000..81e7cbb3
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal31@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal32@2x.png b/Messenger/Resources/Stickers1/stickerlocal32@2x.png
new file mode 100755
index 00000000..3cef1a2b
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal32@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal33@2x.png b/Messenger/Resources/Stickers1/stickerlocal33@2x.png
new file mode 100755
index 00000000..a120f309
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal33@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal34@2x.png b/Messenger/Resources/Stickers1/stickerlocal34@2x.png
new file mode 100755
index 00000000..9dbd1b56
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal34@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal35@2x.png b/Messenger/Resources/Stickers1/stickerlocal35@2x.png
new file mode 100755
index 00000000..d03c1f58
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal35@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal36@2x.png b/Messenger/Resources/Stickers1/stickerlocal36@2x.png
new file mode 100755
index 00000000..01a3d3dd
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal36@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal37@2x.png b/Messenger/Resources/Stickers1/stickerlocal37@2x.png
new file mode 100755
index 00000000..59355541
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal37@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal38@2x.png b/Messenger/Resources/Stickers1/stickerlocal38@2x.png
new file mode 100755
index 00000000..88a6eaa5
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal38@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal39@2x.png b/Messenger/Resources/Stickers1/stickerlocal39@2x.png
new file mode 100755
index 00000000..ddcd0aea
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal39@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal40@2x.png b/Messenger/Resources/Stickers1/stickerlocal40@2x.png
new file mode 100755
index 00000000..ebf7243b
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal40@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal41@2x.png b/Messenger/Resources/Stickers1/stickerlocal41@2x.png
new file mode 100755
index 00000000..969d8123
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal41@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal42@2x.png b/Messenger/Resources/Stickers1/stickerlocal42@2x.png
new file mode 100755
index 00000000..ef0aefa8
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal42@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal43@2x.png b/Messenger/Resources/Stickers1/stickerlocal43@2x.png
new file mode 100755
index 00000000..853f089b
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal43@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal44@2x.png b/Messenger/Resources/Stickers1/stickerlocal44@2x.png
new file mode 100755
index 00000000..85eb08b4
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal44@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal45@2x.png b/Messenger/Resources/Stickers1/stickerlocal45@2x.png
new file mode 100755
index 00000000..e40b96b4
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal45@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal46@2x.png b/Messenger/Resources/Stickers1/stickerlocal46@2x.png
new file mode 100755
index 00000000..669dc2d8
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal46@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal47@2x.png b/Messenger/Resources/Stickers1/stickerlocal47@2x.png
new file mode 100755
index 00000000..148d7bca
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal47@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal48@2x.png b/Messenger/Resources/Stickers1/stickerlocal48@2x.png
new file mode 100755
index 00000000..8add9823
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal48@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal49@2x.png b/Messenger/Resources/Stickers1/stickerlocal49@2x.png
new file mode 100755
index 00000000..d0daa98b
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal49@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal50@2x.png b/Messenger/Resources/Stickers1/stickerlocal50@2x.png
new file mode 100755
index 00000000..4db4d1ed
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal50@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal51@2x.png b/Messenger/Resources/Stickers1/stickerlocal51@2x.png
new file mode 100755
index 00000000..74e7eb19
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal51@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal52@2x.png b/Messenger/Resources/Stickers1/stickerlocal52@2x.png
new file mode 100755
index 00000000..d20695c5
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal52@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal53@2x.png b/Messenger/Resources/Stickers1/stickerlocal53@2x.png
new file mode 100755
index 00000000..85eac7bd
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal53@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal54@2x.png b/Messenger/Resources/Stickers1/stickerlocal54@2x.png
new file mode 100755
index 00000000..f2914e1a
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal54@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal55@2x.png b/Messenger/Resources/Stickers1/stickerlocal55@2x.png
new file mode 100755
index 00000000..2ffb04b3
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal55@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal56@2x.png b/Messenger/Resources/Stickers1/stickerlocal56@2x.png
new file mode 100755
index 00000000..cd13abc0
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal56@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal57@2x.png b/Messenger/Resources/Stickers1/stickerlocal57@2x.png
new file mode 100755
index 00000000..4db3485f
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal57@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal58@2x.png b/Messenger/Resources/Stickers1/stickerlocal58@2x.png
new file mode 100755
index 00000000..d94c5451
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal58@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal59@2x.png b/Messenger/Resources/Stickers1/stickerlocal59@2x.png
new file mode 100755
index 00000000..e634f106
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal59@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal60@2x.png b/Messenger/Resources/Stickers1/stickerlocal60@2x.png
new file mode 100755
index 00000000..b6efab0a
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal60@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal61@2x.png b/Messenger/Resources/Stickers1/stickerlocal61@2x.png
new file mode 100755
index 00000000..de9228c9
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal61@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal62@2x.png b/Messenger/Resources/Stickers1/stickerlocal62@2x.png
new file mode 100755
index 00000000..b5e319f5
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal62@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal63@2x.png b/Messenger/Resources/Stickers1/stickerlocal63@2x.png
new file mode 100755
index 00000000..181d7c84
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal63@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal64@2x.png b/Messenger/Resources/Stickers1/stickerlocal64@2x.png
new file mode 100755
index 00000000..9d49a4e3
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal64@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal65@2x.png b/Messenger/Resources/Stickers1/stickerlocal65@2x.png
new file mode 100755
index 00000000..bb67adf0
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal65@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal66@2x.png b/Messenger/Resources/Stickers1/stickerlocal66@2x.png
new file mode 100755
index 00000000..24b684ac
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal66@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal67@2x.png b/Messenger/Resources/Stickers1/stickerlocal67@2x.png
new file mode 100755
index 00000000..e60b5baf
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal67@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal68@2x.png b/Messenger/Resources/Stickers1/stickerlocal68@2x.png
new file mode 100755
index 00000000..edf4a8cb
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal68@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal69@2x.png b/Messenger/Resources/Stickers1/stickerlocal69@2x.png
new file mode 100755
index 00000000..2eeb244e
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal69@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal70@2x.png b/Messenger/Resources/Stickers1/stickerlocal70@2x.png
new file mode 100755
index 00000000..336353ab
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal70@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal71@2x.png b/Messenger/Resources/Stickers1/stickerlocal71@2x.png
new file mode 100755
index 00000000..7b8004b7
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal71@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal72@2x.png b/Messenger/Resources/Stickers1/stickerlocal72@2x.png
new file mode 100755
index 00000000..afec84b7
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal72@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal73@2x.png b/Messenger/Resources/Stickers1/stickerlocal73@2x.png
new file mode 100755
index 00000000..2c41a2c8
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal73@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal74@2x.png b/Messenger/Resources/Stickers1/stickerlocal74@2x.png
new file mode 100755
index 00000000..b3484948
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal74@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal75@2x.png b/Messenger/Resources/Stickers1/stickerlocal75@2x.png
new file mode 100755
index 00000000..93713834
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal75@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal76@2x.png b/Messenger/Resources/Stickers1/stickerlocal76@2x.png
new file mode 100755
index 00000000..016eb9c0
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal76@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal77@2x.png b/Messenger/Resources/Stickers1/stickerlocal77@2x.png
new file mode 100755
index 00000000..ffa2716d
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal77@2x.png differ
diff --git a/Messenger/Resources/Stickers1/stickerlocal78@2x.png b/Messenger/Resources/Stickers1/stickerlocal78@2x.png
new file mode 100755
index 00000000..8daf97ea
Binary files /dev/null and b/Messenger/Resources/Stickers1/stickerlocal78@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend01@2x.png b/Messenger/Resources/Stickers2/stickersend01@2x.png
new file mode 100644
index 00000000..6d5c74bb
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend01@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend02@2x.png b/Messenger/Resources/Stickers2/stickersend02@2x.png
new file mode 100644
index 00000000..31c1be17
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend02@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend03@2x.png b/Messenger/Resources/Stickers2/stickersend03@2x.png
new file mode 100644
index 00000000..a500b65b
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend03@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend04@2x.png b/Messenger/Resources/Stickers2/stickersend04@2x.png
new file mode 100644
index 00000000..70586a40
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend04@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend05@2x.png b/Messenger/Resources/Stickers2/stickersend05@2x.png
new file mode 100644
index 00000000..43277f15
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend05@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend06@2x.png b/Messenger/Resources/Stickers2/stickersend06@2x.png
new file mode 100644
index 00000000..be597b19
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend06@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend07@2x.png b/Messenger/Resources/Stickers2/stickersend07@2x.png
new file mode 100644
index 00000000..6fb56d9d
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend07@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend08@2x.png b/Messenger/Resources/Stickers2/stickersend08@2x.png
new file mode 100644
index 00000000..188d2cde
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend08@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend09@2x.png b/Messenger/Resources/Stickers2/stickersend09@2x.png
new file mode 100644
index 00000000..a4c2fcc3
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend09@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend10@2x.png b/Messenger/Resources/Stickers2/stickersend10@2x.png
new file mode 100644
index 00000000..c6e85809
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend10@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend11@2x.png b/Messenger/Resources/Stickers2/stickersend11@2x.png
new file mode 100644
index 00000000..7badf535
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend11@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend12@2x.png b/Messenger/Resources/Stickers2/stickersend12@2x.png
new file mode 100644
index 00000000..3fdc6879
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend12@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend13@2x.png b/Messenger/Resources/Stickers2/stickersend13@2x.png
new file mode 100644
index 00000000..c8f7020b
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend13@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend14@2x.png b/Messenger/Resources/Stickers2/stickersend14@2x.png
new file mode 100644
index 00000000..ca97e0b9
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend14@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend15@2x.png b/Messenger/Resources/Stickers2/stickersend15@2x.png
new file mode 100644
index 00000000..bc6a17e9
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend15@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend16@2x.png b/Messenger/Resources/Stickers2/stickersend16@2x.png
new file mode 100644
index 00000000..9ec6807e
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend16@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend17@2x.png b/Messenger/Resources/Stickers2/stickersend17@2x.png
new file mode 100644
index 00000000..e558bf2a
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend17@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend18@2x.png b/Messenger/Resources/Stickers2/stickersend18@2x.png
new file mode 100644
index 00000000..df55f4ed
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend18@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend19@2x.png b/Messenger/Resources/Stickers2/stickersend19@2x.png
new file mode 100644
index 00000000..b5275496
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend19@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend20@2x.png b/Messenger/Resources/Stickers2/stickersend20@2x.png
new file mode 100644
index 00000000..ecc35070
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend20@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend21@2x.png b/Messenger/Resources/Stickers2/stickersend21@2x.png
new file mode 100644
index 00000000..e9e10c99
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend21@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend22@2x.png b/Messenger/Resources/Stickers2/stickersend22@2x.png
new file mode 100644
index 00000000..2a0064bd
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend22@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend23@2x.png b/Messenger/Resources/Stickers2/stickersend23@2x.png
new file mode 100644
index 00000000..8dec9d83
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend23@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend24@2x.png b/Messenger/Resources/Stickers2/stickersend24@2x.png
new file mode 100644
index 00000000..bd3caaa4
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend24@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend25@2x.png b/Messenger/Resources/Stickers2/stickersend25@2x.png
new file mode 100644
index 00000000..0a2387bc
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend25@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend26@2x.png b/Messenger/Resources/Stickers2/stickersend26@2x.png
new file mode 100644
index 00000000..648b3ec8
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend26@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend27@2x.png b/Messenger/Resources/Stickers2/stickersend27@2x.png
new file mode 100644
index 00000000..c8c08276
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend27@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend28@2x.png b/Messenger/Resources/Stickers2/stickersend28@2x.png
new file mode 100644
index 00000000..fa692fbf
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend28@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend29@2x.png b/Messenger/Resources/Stickers2/stickersend29@2x.png
new file mode 100644
index 00000000..a77db812
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend29@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend30@2x.png b/Messenger/Resources/Stickers2/stickersend30@2x.png
new file mode 100644
index 00000000..435ec411
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend30@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend31@2x.png b/Messenger/Resources/Stickers2/stickersend31@2x.png
new file mode 100644
index 00000000..b1de0a73
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend31@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend32@2x.png b/Messenger/Resources/Stickers2/stickersend32@2x.png
new file mode 100644
index 00000000..b5815fc2
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend32@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend33@2x.png b/Messenger/Resources/Stickers2/stickersend33@2x.png
new file mode 100644
index 00000000..b0cb77bf
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend33@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend34@2x.png b/Messenger/Resources/Stickers2/stickersend34@2x.png
new file mode 100644
index 00000000..594e2c1f
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend34@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend35@2x.png b/Messenger/Resources/Stickers2/stickersend35@2x.png
new file mode 100644
index 00000000..38793f7a
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend35@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend36@2x.png b/Messenger/Resources/Stickers2/stickersend36@2x.png
new file mode 100644
index 00000000..c5e57c21
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend36@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend37@2x.png b/Messenger/Resources/Stickers2/stickersend37@2x.png
new file mode 100644
index 00000000..2a9d2f39
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend37@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend38@2x.png b/Messenger/Resources/Stickers2/stickersend38@2x.png
new file mode 100644
index 00000000..0be07246
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend38@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend39@2x.png b/Messenger/Resources/Stickers2/stickersend39@2x.png
new file mode 100644
index 00000000..73764105
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend39@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend40@2x.png b/Messenger/Resources/Stickers2/stickersend40@2x.png
new file mode 100644
index 00000000..4e0c67d2
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend40@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend41@2x.png b/Messenger/Resources/Stickers2/stickersend41@2x.png
new file mode 100644
index 00000000..0e92ff8d
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend41@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend42@2x.png b/Messenger/Resources/Stickers2/stickersend42@2x.png
new file mode 100644
index 00000000..d55b1870
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend42@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend43@2x.png b/Messenger/Resources/Stickers2/stickersend43@2x.png
new file mode 100644
index 00000000..d0fca5e8
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend43@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend44@2x.png b/Messenger/Resources/Stickers2/stickersend44@2x.png
new file mode 100644
index 00000000..f0d6fe7a
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend44@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend45@2x.png b/Messenger/Resources/Stickers2/stickersend45@2x.png
new file mode 100644
index 00000000..9f529084
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend45@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend46@2x.png b/Messenger/Resources/Stickers2/stickersend46@2x.png
new file mode 100644
index 00000000..50fd1396
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend46@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend47@2x.png b/Messenger/Resources/Stickers2/stickersend47@2x.png
new file mode 100644
index 00000000..dd1b9264
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend47@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend48@2x.png b/Messenger/Resources/Stickers2/stickersend48@2x.png
new file mode 100644
index 00000000..58bca024
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend48@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend49@2x.png b/Messenger/Resources/Stickers2/stickersend49@2x.png
new file mode 100644
index 00000000..e0f0262d
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend49@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend50@2x.png b/Messenger/Resources/Stickers2/stickersend50@2x.png
new file mode 100644
index 00000000..1b56c786
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend50@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend51@2x.png b/Messenger/Resources/Stickers2/stickersend51@2x.png
new file mode 100644
index 00000000..370966b2
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend51@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend52@2x.png b/Messenger/Resources/Stickers2/stickersend52@2x.png
new file mode 100644
index 00000000..171f2867
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend52@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend53@2x.png b/Messenger/Resources/Stickers2/stickersend53@2x.png
new file mode 100644
index 00000000..ce07a1b2
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend53@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend54@2x.png b/Messenger/Resources/Stickers2/stickersend54@2x.png
new file mode 100644
index 00000000..24651c15
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend54@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend55@2x.png b/Messenger/Resources/Stickers2/stickersend55@2x.png
new file mode 100644
index 00000000..1a791e24
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend55@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend56@2x.png b/Messenger/Resources/Stickers2/stickersend56@2x.png
new file mode 100644
index 00000000..7341a93a
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend56@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend57@2x.png b/Messenger/Resources/Stickers2/stickersend57@2x.png
new file mode 100644
index 00000000..b4f075a7
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend57@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend58@2x.png b/Messenger/Resources/Stickers2/stickersend58@2x.png
new file mode 100644
index 00000000..af79d2e9
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend58@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend59@2x.png b/Messenger/Resources/Stickers2/stickersend59@2x.png
new file mode 100644
index 00000000..068fa72d
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend59@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend60@2x.png b/Messenger/Resources/Stickers2/stickersend60@2x.png
new file mode 100644
index 00000000..ba58c679
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend60@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend61@2x.png b/Messenger/Resources/Stickers2/stickersend61@2x.png
new file mode 100644
index 00000000..85c6990e
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend61@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend62@2x.png b/Messenger/Resources/Stickers2/stickersend62@2x.png
new file mode 100644
index 00000000..a01c4b77
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend62@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend63@2x.png b/Messenger/Resources/Stickers2/stickersend63@2x.png
new file mode 100644
index 00000000..6a8874fd
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend63@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend64@2x.png b/Messenger/Resources/Stickers2/stickersend64@2x.png
new file mode 100644
index 00000000..812dfd09
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend64@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend65@2x.png b/Messenger/Resources/Stickers2/stickersend65@2x.png
new file mode 100644
index 00000000..82c9149e
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend65@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend66@2x.png b/Messenger/Resources/Stickers2/stickersend66@2x.png
new file mode 100644
index 00000000..6479869c
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend66@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend67@2x.png b/Messenger/Resources/Stickers2/stickersend67@2x.png
new file mode 100644
index 00000000..00d246ba
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend67@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend68@2x.png b/Messenger/Resources/Stickers2/stickersend68@2x.png
new file mode 100644
index 00000000..dce78533
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend68@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend69@2x.png b/Messenger/Resources/Stickers2/stickersend69@2x.png
new file mode 100644
index 00000000..f8053f21
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend69@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend70@2x.png b/Messenger/Resources/Stickers2/stickersend70@2x.png
new file mode 100644
index 00000000..fcce540e
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend70@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend71@2x.png b/Messenger/Resources/Stickers2/stickersend71@2x.png
new file mode 100644
index 00000000..ee179b83
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend71@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend72@2x.png b/Messenger/Resources/Stickers2/stickersend72@2x.png
new file mode 100644
index 00000000..d484289b
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend72@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend73@2x.png b/Messenger/Resources/Stickers2/stickersend73@2x.png
new file mode 100644
index 00000000..950a79f1
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend73@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend74@2x.png b/Messenger/Resources/Stickers2/stickersend74@2x.png
new file mode 100644
index 00000000..f6defbf0
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend74@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend75@2x.png b/Messenger/Resources/Stickers2/stickersend75@2x.png
new file mode 100644
index 00000000..eca4dbe7
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend75@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend76@2x.png b/Messenger/Resources/Stickers2/stickersend76@2x.png
new file mode 100644
index 00000000..3b9d609a
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend76@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend77@2x.png b/Messenger/Resources/Stickers2/stickersend77@2x.png
new file mode 100644
index 00000000..6342e172
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend77@2x.png differ
diff --git a/Messenger/Resources/Stickers2/stickersend78@2x.png b/Messenger/Resources/Stickers2/stickersend78@2x.png
new file mode 100644
index 00000000..48820504
Binary files /dev/null and b/Messenger/Resources/Stickers2/stickersend78@2x.png differ
diff --git a/Messenger/Resources/Wallpapers/wallpapers01@2x.jpg b/Messenger/Resources/Wallpapers/wallpapers01@2x.jpg
new file mode 100644
index 00000000..ff034c02
Binary files /dev/null and b/Messenger/Resources/Wallpapers/wallpapers01@2x.jpg differ
diff --git a/Messenger/Resources/Wallpapers/wallpapers02@2x.jpg b/Messenger/Resources/Wallpapers/wallpapers02@2x.jpg
new file mode 100644
index 00000000..c92bf53f
Binary files /dev/null and b/Messenger/Resources/Wallpapers/wallpapers02@2x.jpg differ
diff --git a/Messenger/Resources/Wallpapers/wallpapers03@2x.jpg b/Messenger/Resources/Wallpapers/wallpapers03@2x.jpg
new file mode 100644
index 00000000..5e9f2436
Binary files /dev/null and b/Messenger/Resources/Wallpapers/wallpapers03@2x.jpg differ
diff --git a/Messenger/Resources/Wallpapers/wallpapers04@2x.jpg b/Messenger/Resources/Wallpapers/wallpapers04@2x.jpg
new file mode 100644
index 00000000..2b033c5e
Binary files /dev/null and b/Messenger/Resources/Wallpapers/wallpapers04@2x.jpg differ
diff --git a/Messenger/Resources/Wallpapers/wallpapers05@2x.jpg b/Messenger/Resources/Wallpapers/wallpapers05@2x.jpg
new file mode 100644
index 00000000..5b792de6
Binary files /dev/null and b/Messenger/Resources/Wallpapers/wallpapers05@2x.jpg differ
diff --git a/Messenger/Resources/Wallpapers/wallpapers06@2x.jpg b/Messenger/Resources/Wallpapers/wallpapers06@2x.jpg
new file mode 100644
index 00000000..92ce9772
Binary files /dev/null and b/Messenger/Resources/Wallpapers/wallpapers06@2x.jpg differ
diff --git a/Messenger/Resources/Wallpapers/wallpapers07@2x.jpg b/Messenger/Resources/Wallpapers/wallpapers07@2x.jpg
new file mode 100644
index 00000000..ee9f9241
Binary files /dev/null and b/Messenger/Resources/Wallpapers/wallpapers07@2x.jpg differ
diff --git a/Messenger/Resources/Wallpapers/wallpapers08@2x.jpg b/Messenger/Resources/Wallpapers/wallpapers08@2x.jpg
new file mode 100644
index 00000000..963492cf
Binary files /dev/null and b/Messenger/Resources/Wallpapers/wallpapers08@2x.jpg differ
diff --git a/Messenger/Resources/Wallpapers/wallpapers09@2x.jpg b/Messenger/Resources/Wallpapers/wallpapers09@2x.jpg
new file mode 100644
index 00000000..049f5717
Binary files /dev/null and b/Messenger/Resources/Wallpapers/wallpapers09@2x.jpg differ
diff --git a/Messenger/Resources/Wallpapers/wallpapers10@2x.jpg b/Messenger/Resources/Wallpapers/wallpapers10@2x.jpg
new file mode 100644
index 00000000..34fe7fc3
Binary files /dev/null and b/Messenger/Resources/Wallpapers/wallpapers10@2x.jpg differ
diff --git a/Messenger/Resources/Web/privacy.html b/Messenger/Resources/Web/privacy.html
new file mode 100755
index 00000000..ad822771
--- /dev/null
+++ b/Messenger/Resources/Web/privacy.html
@@ -0,0 +1,57 @@
+
+
+
+
+
Privacy Policy
+
This privacy policy governs your use of the software application Related Chat ("Application") for mobile devices that was created by Related Code. The Application is a full native iPhone app to create realtime, text based group or private chat with Firebase.
+
+
What information does the Application obtain and how is it used?
+
+
User Provided Information
+
The Application obtains the information you provide when you download and register the Application. Registration with us is optional. However, please keep in mind that you may not be able to use some of the features offered by the Application unless you register with us.
+
When you register with us and use the Application, you generally provide (a) your name, email address, age, user name, password and other registration information; (b) transaction-related information, such as when you make purchases, respond to any offers, or download or use applications from us; (c) information you provide us when you contact us for help; (d) credit card information for purchase and use of the Application, and; (e) information you enter into our system when using the Application, such as contact information and project management information.
+
We may also use the information you provided us to contact your from time to time to provide you with important information, required notices and marketing promotions.
+
+
Automatically Collected Information
+
In addition, the Application may collect certain information automatically, including, but not limited to, the type of mobile device you use, your mobile devices unique device ID, the IP address of your mobile device, your mobile operating system, the type of mobile Internet browsers you use, and information about the way you use the Application.
+
+
Does the Application collect precise real time location information of the device?
+
This Application collects precise information about the location of your mobile device.
+
+
Do third parties see and/or have access to information obtained by the Application?
+
Only aggregated, anonymized data is periodically transmitted to external services to help us improve the Application and our service. We will share your information with third parties only in the ways that are described in this privacy statement.
+
We may disclose User Provided and Automatically Collected Information:
+
+
as required by law, such as to comply with a subpoena, or similar legal process;
+
when we believe in good faith that disclosure is necessary to protect our rights, protect your safety or the safety of others, investigate fraud, or respond to a government request;
+
with our trusted services providers who work on our behalf, do not have an independent use of the information we disclose to them, and have agreed to adhere to the rules set forth in this privacy statement.
+
if Related Code is involved in a merger, acquisition, or sale of all or a portion of its assets, you will be notified via email and/or a prominent notice on our Web site of any change in ownership or uses of this information, as well as any choices you may have regarding this information.
+
+
+
What are my opt-out rights?
+
You can stop all collection of information by the Application easily by uninstalling the Application. You may use the standard uninstall processes as may be available as part of your mobile device or via the mobile application marketplace or network. You can also request to opt-out via email, at info@relatedcode.com.
+
+
Data Retention Policy, Managing Your Information
+
We will retain User Provided data for as long as you use the Application and for a reasonable time thereafter. We will retain Automatically Collected information for up to 24 months and thereafter may store it in aggregate. If you would like us to delete User Provided Data that you have provided via the Application, please contact us at info@relatedcode.com and we will respond in a reasonable time. Please note that some or all of the User Provided Data may be required in order for the Application to function properly.
+
+
Children
+
We do not use the Application to knowingly solicit data from or market to children under the age of 13. If a parent or guardian becomes aware that his or her child has provided us with information without their consent, he or she should contact us at info@relatedcode.com. We will delete such information from our files within a reasonable time.
+
+
Security
+
We are concerned about safeguarding the confidentiality of your information. We provide physical, electronic, and procedural safeguards to protect information we process and maintain. For example, we limit access to this information to authorized employees and contractors who need to know that information in order to operate, develop or improve our Application. Please be aware that, although we endeavor provide reasonable security for information we process and maintain, no security system can prevent all potential security breaches.
+
+
Changes
+
This Privacy Policy may be updated from time to time for any reason. We will notify you of any changes to our Privacy Policy by posting the new Privacy Policy relatedcode.com and informing you via email or text message. You are advised to consult this Privacy Policy regularly for any changes, as continued use is deemed approval of all changes. You can check the history of this policy by clicking here.
+
+
Your Consent
+
By using the Application, you are consenting to our processing of your information as set forth in this Privacy Policy now and as amended by us. Processing means using cookies on a computer/hand held device or using or touching information in any way, including, but not limited to, collecting, storing, deleting, using, combining and disclosing information, all of which activities will take place in the United States. If you reside outside the United States your information will be transferred, processed and stored there under United States privacy standards.
+
+
Contact us
+
If you have any questions regarding privacy while using the Application, or have questions about our practices, please contact us via email at info@relatedcode.com.
+
+
\ No newline at end of file
diff --git a/Messenger/Resources/Web/terms.html b/Messenger/Resources/Web/terms.html
new file mode 100755
index 00000000..e36c1d98
--- /dev/null
+++ b/Messenger/Resources/Web/terms.html
@@ -0,0 +1,18 @@
+
+
+
+
+
Terms of Service
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras nec nunc ac neque blandit dapibus ac eget orci. Pellentesque in feugiat est. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean convallis hendrerit arcu, a imperdiet ipsum lacinia non. Suspendisse scelerisque accumsan nibh, eget fermentum leo ultricies et. Ut tempus auctor erat ut aliquam. Vestibulum elementum congue diam, eget facilisis ex viverra sit amet.
+
Pellentesque commodo, magna ut semper mattis, ante sem pellentesque urna, vitae luctus felis velit eu felis. Cras non mi ornare est lacinia tempus nec vel nunc. Nullam sed felis nisi. Suspendisse purus diam, efficitur et felis nec, egestas lacinia ante. Quisque nulla odio, venenatis quis lorem eu, cursus efficitur quam. In risus ligula, luctus id mi vitae, hendrerit venenatis mi. Suspendisse euismod urna enim, id maximus lectus iaculis vitae.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras nec nunc ac neque blandit dapibus ac eget orci. Pellentesque in feugiat est. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean convallis hendrerit arcu, a imperdiet ipsum lacinia non. Suspendisse scelerisque accumsan nibh, eget fermentum leo ultricies et. Ut tempus auctor erat ut aliquam. Vestibulum elementum congue diam, eget facilisis ex viverra sit amet.
+
Pellentesque commodo, magna ut semper mattis, ante sem pellentesque urna, vitae luctus felis velit eu felis. Cras non mi ornare est lacinia tempus nec vel nunc. Nullam sed felis nisi. Suspendisse purus diam, efficitur et felis nec, egestas lacinia ante. Quisque nulla odio, venenatis quis lorem eu, cursus efficitur quam. In risus ligula, luctus id mi vitae, hendrerit venenatis mi. Suspendisse euismod urna enim, id maximus lectus iaculis vitae.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras nec nunc ac neque blandit dapibus ac eget orci. Pellentesque in feugiat est. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean convallis hendrerit arcu, a imperdiet ipsum lacinia non. Suspendisse scelerisque accumsan nibh, eget fermentum leo ultricies et. Ut tempus auctor erat ut aliquam. Vestibulum elementum congue diam, eget facilisis ex viverra sit amet.
+
Pellentesque commodo, magna ut semper mattis, ante sem pellentesque urna, vitae luctus felis velit eu felis. Cras non mi ornare est lacinia tempus nec vel nunc. Nullam sed felis nisi. Suspendisse purus diam, efficitur et felis nec, egestas lacinia ante. Quisque nulla odio, venenatis quis lorem eu, cursus efficitur quam. In risus ligula, luctus id mi vitae, hendrerit venenatis mi. Suspendisse euismod urna enim, id maximus lectus iaculis vitae.
+
+
\ No newline at end of file
diff --git a/Messenger/Vendors/RCAudioPlayer/RCAudioPlayer.swift b/Messenger/Vendors/RCAudioPlayer/RCAudioPlayer.swift
new file mode 100644
index 00000000..8a57cf6d
--- /dev/null
+++ b/Messenger/Vendors/RCAudioPlayer/RCAudioPlayer.swift
@@ -0,0 +1,99 @@
+//
+// Copyright (c) 2017 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCAudioPlayer: NSObject {
+
+ private var soundIDs: [String : SystemSoundID] = [:]
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: RCAudioPlayer = {
+ let instance = RCAudioPlayer()
+ return instance
+ } ()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func playSound(_ path: String) {
+
+ if (path.count != 0) {
+ playSound(path, isAlert: false)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func playAlert(_ path: String) {
+
+ if (path.count != 0) {
+ playSound(path, isAlert: true)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func playVibrateSound() {
+
+ AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func stopAllSounds() {
+
+ unloadAllSounds()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func stopSound(_ path: String) {
+
+ if (path.count != 0) {
+ unloadSound(path)
+ }
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func playSound(_ path: String, isAlert: Bool) {
+
+ var soundID: SystemSoundID = 0
+
+ if (soundIDs[path] == nil) {
+ let url = URL(fileURLWithPath: path) as CFURL
+ AudioServicesCreateSystemSoundID(url, &soundID)
+ soundIDs[path] = soundID
+ } else {
+ soundID = soundIDs[path]!
+ }
+
+ if (soundID != 0) {
+ if (isAlert) {
+ AudioServicesPlayAlertSound(soundID)
+ } else {
+ AudioServicesPlaySystemSound(soundID)
+ }
+ }
+ }
+
+ // MARK: -
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func unloadAllSounds() {
+
+ for path in soundIDs.keys {
+ unloadSound(path)
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func unloadSound(_ path: String) {
+
+ if let soundID = soundIDs[path] {
+ AudioServicesDisposeSystemSoundID(soundID)
+ soundIDs.removeValue(forKey: path)
+ }
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/FooterCells/RCBubbleFooterCell.swift b/Messenger/Vendors/RCMessageKit/Cell/FooterCells/RCBubbleFooterCell.swift
new file mode 100644
index 00000000..2c42999f
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/FooterCells/RCBubbleFooterCell.swift
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCBubbleFooterCell: UITableViewCell {
+
+ var labelBubbleFooter: UILabel!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ backgroundColor = UIColor.clear
+
+ if (labelBubbleFooter == nil) {
+ labelBubbleFooter = UILabel()
+ labelBubbleFooter.font = RCMessages().bubbleFooterFont
+ labelBubbleFooter.textColor = RCMessages().bubbleFooterColor
+ contentView.addSubview(labelBubbleFooter)
+ }
+
+ labelBubbleFooter.textAlignment = rcmessage.incoming ? .left : .right
+ labelBubbleFooter.text = messagesView.textBubbleFooter(indexPath)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func layoutSubviews() {
+
+ super.layoutSubviews()
+
+ let widthTable = messagesView.tableView.frame.size.width
+
+ let width: CGFloat = widthTable - RCMessages().bubbleFooterLeft - RCMessages().bubbleFooterRight
+ let height: CGFloat = (labelBubbleFooter.text != nil) ? RCMessages().bubbleFooterHeight : 0
+
+ labelBubbleFooter.frame = CGRect(x: RCMessages().bubbleFooterLeft, y: 0, width: width, height: height)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ return (messagesView.textBubbleFooter(indexPath) != nil) ? RCMessages().bubbleFooterHeight : 0
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/FooterCells/RCSectionFooterCell.swift b/Messenger/Vendors/RCMessageKit/Cell/FooterCells/RCSectionFooterCell.swift
new file mode 100644
index 00000000..3a485896
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/FooterCells/RCSectionFooterCell.swift
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCSectionFooterCell: UITableViewCell {
+
+ var labelSectionFooter: UILabel!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ backgroundColor = UIColor.clear
+
+ if (labelSectionFooter == nil) {
+ labelSectionFooter = UILabel()
+ labelSectionFooter.font = RCMessages().sectionFooterFont
+ labelSectionFooter.textColor = RCMessages().sectionFooterColor
+ contentView.addSubview(labelSectionFooter)
+ }
+
+ labelSectionFooter.textAlignment = rcmessage.incoming ? .left : .right
+ labelSectionFooter.text = messagesView.textSectionFooter(indexPath)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func layoutSubviews() {
+
+ super.layoutSubviews()
+
+ let widthTable = messagesView.tableView.frame.size.width
+
+ let width: CGFloat = widthTable - RCMessages().sectionFooterLeft - RCMessages().sectionFooterRight
+ let height: CGFloat = (labelSectionFooter.text != nil) ? RCMessages().sectionFooterHeight : 0
+
+ labelSectionFooter.frame = CGRect(x: RCMessages().sectionFooterLeft, y: 0, width: width, height: height)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ return (messagesView.textSectionFooter(indexPath) != nil) ? RCMessages().sectionFooterHeight : 0
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/HeaderCells/RCBubbleHeaderCell.swift b/Messenger/Vendors/RCMessageKit/Cell/HeaderCells/RCBubbleHeaderCell.swift
new file mode 100644
index 00000000..804849d0
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/HeaderCells/RCBubbleHeaderCell.swift
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCBubbleHeaderCell: UITableViewCell {
+
+ var labelBubbleHeader: UILabel!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ backgroundColor = UIColor.clear
+
+ if (labelBubbleHeader == nil) {
+ labelBubbleHeader = UILabel()
+ labelBubbleHeader.font = RCMessages().bubbleHeaderFont
+ labelBubbleHeader.textColor = RCMessages().bubbleHeaderColor
+ contentView.addSubview(labelBubbleHeader)
+ }
+
+ labelBubbleHeader.textAlignment = rcmessage.incoming ? .left : .right
+ labelBubbleHeader.text = messagesView.textBubbleHeader(indexPath)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func layoutSubviews() {
+
+ super.layoutSubviews()
+
+ let widthTable = messagesView.tableView.frame.size.width
+
+ let width: CGFloat = widthTable - RCMessages().bubbleHeaderLeft - RCMessages().bubbleHeaderRight
+ let height: CGFloat = (labelBubbleHeader.text != nil) ? RCMessages().bubbleHeaderHeight : 0
+
+ labelBubbleHeader.frame = CGRect(x: RCMessages().bubbleHeaderLeft, y: 0, width: width, height: height)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ return (messagesView.textBubbleHeader(indexPath) != nil) ? RCMessages().bubbleHeaderHeight : 0
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/HeaderCells/RCSectionHeaderCell.swift b/Messenger/Vendors/RCMessageKit/Cell/HeaderCells/RCSectionHeaderCell.swift
new file mode 100644
index 00000000..fbd48b77
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/HeaderCells/RCSectionHeaderCell.swift
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCSectionHeaderCell: UITableViewCell {
+
+ var labelSectionHeader: UILabel!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ backgroundColor = UIColor.clear
+
+ if (labelSectionHeader == nil) {
+ labelSectionHeader = UILabel()
+ labelSectionHeader.font = RCMessages().sectionHeaderFont
+ labelSectionHeader.textColor = RCMessages().sectionHeaderColor
+ contentView.addSubview(labelSectionHeader)
+ }
+
+ labelSectionHeader.textAlignment = rcmessage.incoming ? .center : .center
+ labelSectionHeader.text = messagesView.textSectionHeader(indexPath)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func layoutSubviews() {
+
+ super.layoutSubviews()
+
+ let widthTable = messagesView.tableView.frame.size.width
+
+ let width: CGFloat = widthTable - RCMessages().sectionHeaderLeft - RCMessages().sectionHeaderRight
+ let height: CGFloat = (labelSectionHeader.text != nil) ? RCMessages().sectionHeaderHeight : 0
+
+ labelSectionHeader.frame = CGRect(x: RCMessages().sectionHeaderLeft, y: 0, width: width, height: height)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ return (messagesView.textSectionHeader(indexPath) != nil) ? RCMessages().sectionHeaderHeight : 0
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/MessageCells/01_Text/RCTextMessageCell.swift b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/01_Text/RCTextMessageCell.swift
new file mode 100644
index 00000000..289259fe
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/01_Text/RCTextMessageCell.swift
@@ -0,0 +1,84 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCTextMessageCell: RCMessageCell {
+
+ var textView: UITextView!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ super.bindData(indexPath, messagesView: messagesView)
+
+ viewBubble.backgroundColor = rcmessage.incoming ? RCMessages().textBubbleColorIncoming : RCMessages().textBubbleColorOutgoing
+
+ if (textView == nil) {
+ textView = UITextView()
+ textView.font = RCMessages().textFont
+ textView.isEditable = false
+ textView.isSelectable = false
+ textView.isScrollEnabled = false
+ textView.isUserInteractionEnabled = false
+ textView.backgroundColor = UIColor.clear
+ textView.textContainer.lineFragmentPadding = 0
+ textView.textContainerInset = RCMessages().textInset
+ viewBubble.addSubview(textView)
+ }
+
+ textView.textColor = rcmessage.incoming ? RCMessages().textTextColorIncoming : RCMessages().textTextColorOutgoing
+
+ textView.text = rcmessage.text
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func layoutSubviews() {
+
+ let size = RCTextMessageCell.size(indexPath, messagesView: messagesView)
+
+ super.layoutSubviews(size)
+
+ textView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ let size = self.size(indexPath, messagesView: messagesView)
+ return size.height
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func size(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGSize {
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ let widthTable = messagesView.tableView.frame.size.width
+
+ let maxwidth = (0.6 * widthTable) - RCMessages().textInsetLeft - RCMessages().textInsetRight
+
+ let rect = rcmessage.text.boundingRect(with: CGSize(width: maxwidth, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: RCMessages().textFont], context: nil)
+
+ let width = rect.size.width + RCMessages().textInsetLeft + RCMessages().textInsetRight
+ let height = rect.size.height + RCMessages().textInsetTop + RCMessages().textInsetBottom
+
+ return CGSize(width: CGFloat.maximum(width, RCMessages().textBubbleWidthMin), height: CGFloat.maximum(height, RCMessages().textBubbleHeightMin))
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/MessageCells/02_Emoji/RCEmojiMessageCell.swift b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/02_Emoji/RCEmojiMessageCell.swift
new file mode 100644
index 00000000..fdf65014
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/02_Emoji/RCEmojiMessageCell.swift
@@ -0,0 +1,82 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCEmojiMessageCell: RCMessageCell {
+
+ var textView: UITextView!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ super.bindData(indexPath, messagesView: messagesView)
+
+ viewBubble.backgroundColor = rcmessage.incoming ? RCMessages().emojiBubbleColorIncoming : RCMessages().emojiBubbleColorOutgoing
+
+ if (textView == nil) {
+ textView = UITextView()
+ textView.font = RCMessages().emojiFont
+ textView.isEditable = false
+ textView.isSelectable = false
+ textView.isScrollEnabled = false
+ textView.isUserInteractionEnabled = false
+ textView.backgroundColor = UIColor.clear
+ textView.textContainer.lineFragmentPadding = 0
+ textView.textContainerInset = RCMessages().emojiInset
+ viewBubble.addSubview(textView)
+ }
+
+ textView.text = rcmessage.text
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func layoutSubviews() {
+
+ let size = RCEmojiMessageCell.size(indexPath, messagesView: messagesView)
+
+ super.layoutSubviews(size)
+
+ textView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ let size = self.size(indexPath, messagesView: messagesView)
+ return size.height
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func size(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGSize {
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ let widthTable = messagesView.tableView.frame.size.width
+
+ let maxwidth = (0.6 * widthTable) - RCMessages().emojiInsetLeft - RCMessages().emojiInsetRight
+
+ let rect = rcmessage.text.boundingRect(with: CGSize(width: maxwidth, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: RCMessages().emojiFont], context: nil)
+
+ let width = rect.size.width + RCMessages().emojiInsetLeft + RCMessages().emojiInsetRight
+ let height = rect.size.height + RCMessages().emojiInsetTop + RCMessages().emojiInsetBottom
+
+ return CGSize(width: CGFloat.maximum(width, RCMessages().emojiBubbleWidthMin), height: CGFloat.maximum(height, RCMessages().emojiBubbleHeightMin))
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/MessageCells/03_Picture/RCPictureMessageCell.swift b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/03_Picture/RCPictureMessageCell.swift
new file mode 100644
index 00000000..7d0e66ed
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/03_Picture/RCPictureMessageCell.swift
@@ -0,0 +1,111 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCPictureMessageCell: RCMessageCell {
+
+ var imageViewX: UIImageView!
+ var imageManual: UIImageView!
+ var spinner: UIActivityIndicatorView!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ super.bindData(indexPath, messagesView: messagesView)
+
+ viewBubble.backgroundColor = rcmessage.incoming ? RCMessages().pictureBubbleColorIncoming : RCMessages().pictureBubbleColorOutgoing
+
+ if (imageViewX == nil) {
+ imageViewX = UIImageView()
+ imageViewX.layer.masksToBounds = true
+ imageViewX.layer.cornerRadius = RCMessages().bubbleRadius
+ viewBubble.addSubview(imageViewX)
+ }
+
+ if (spinner == nil) {
+ spinner = UIActivityIndicatorView(style: .white)
+ viewBubble.addSubview(spinner)
+ }
+
+ if (imageManual == nil) {
+ imageManual = UIImageView(image: RCMessages().pictureImageManual)
+ viewBubble.addSubview(imageManual)
+ }
+
+ if (rcmessage.status == RC_STATUS_LOADING) {
+ imageViewX.image = nil
+ spinner.startAnimating()
+ imageManual.isHidden = true
+ }
+
+ if (rcmessage.status == RC_STATUS_SUCCEED) {
+ imageViewX.image = rcmessage.picture_image
+ spinner.stopAnimating()
+ imageManual.isHidden = true
+ }
+
+ if (rcmessage.status == RC_STATUS_MANUAL) {
+ imageViewX.image = nil
+ spinner.stopAnimating()
+ imageManual.isHidden = false
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func layoutSubviews() {
+
+ let size = RCPictureMessageCell.size(indexPath, messagesView: messagesView)
+
+ super.layoutSubviews(size)
+
+ imageViewX.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
+
+ let widthSpinner = spinner.frame.size.width
+ let heightSpinner = spinner.frame.size.height
+ let xSpinner = (size.width - widthSpinner) / 2
+ let ySpinner = (size.height - heightSpinner) / 2
+ spinner.frame = CGRect(x: xSpinner, y: ySpinner, width: widthSpinner, height: heightSpinner)
+
+ let widthManual = imageManual.image!.size.width
+ let heightManual = imageManual.image!.size.height
+ let xManual = (size.width - widthManual) / 2
+ let yManual = (size.height - heightManual) / 2
+ imageManual.frame = CGRect(x: xManual, y: yManual, width: widthManual, height: heightManual)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ let size = self.size(indexPath, messagesView: messagesView)
+ return size.height
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func size(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGSize {
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ let picture_width = CGFloat(rcmessage.picture_width)
+ let picture_height = CGFloat(rcmessage.picture_height)
+
+ let width = CGFloat.minimum(RCMessages().pictureBubbleWidth, picture_width)
+ return CGSize(width: width, height: picture_height * width / picture_width)
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/MessageCells/04_Video/RCVideoMessageCell.swift b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/04_Video/RCVideoMessageCell.swift
new file mode 100644
index 00000000..37695c73
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/04_Video/RCVideoMessageCell.swift
@@ -0,0 +1,120 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCVideoMessageCell: RCMessageCell {
+
+ var imageViewX: UIImageView!
+ var imagePlay: UIImageView!
+ var imageManual: UIImageView!
+ var spinner: UIActivityIndicatorView!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ super.bindData(indexPath, messagesView: messagesView)
+
+ viewBubble.backgroundColor = rcmessage.incoming ? RCMessages().videoBubbleColorIncoming : RCMessages().videoBubbleColorOutgoing
+
+ if (imageViewX == nil) {
+ imageViewX = UIImageView()
+ imageViewX.layer.masksToBounds = true
+ imageViewX.layer.cornerRadius = RCMessages().bubbleRadius
+ viewBubble.addSubview(imageViewX)
+ }
+
+ if (imagePlay == nil) {
+ imagePlay = UIImageView(image: RCMessages().videoImagePlay)
+ viewBubble.addSubview(imagePlay)
+ }
+
+ if (spinner == nil) {
+ spinner = UIActivityIndicatorView(style: .white)
+ viewBubble.addSubview(spinner)
+ }
+
+ if (imageManual == nil) {
+ imageManual = UIImageView(image: RCMessages().videoImageManual)
+ viewBubble.addSubview(imageManual)
+ }
+
+ if (rcmessage.status == RC_STATUS_LOADING) {
+ imageViewX.image = nil
+ imagePlay.isHidden = true
+ spinner.startAnimating()
+ imageManual.isHidden = true
+ }
+
+ if (rcmessage.status == RC_STATUS_SUCCEED) {
+ imageViewX.image = rcmessage.video_thumbnail
+ imagePlay.isHidden = false
+ spinner.stopAnimating()
+ imageManual.isHidden = true
+ }
+
+ if (rcmessage.status == RC_STATUS_MANUAL) {
+ imageViewX.image = nil
+ imagePlay.isHidden = true
+ spinner.stopAnimating()
+ imageManual.isHidden = false
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func layoutSubviews() {
+
+ let size = RCVideoMessageCell.size(indexPath, messagesView: messagesView)
+
+ super.layoutSubviews(size)
+
+ imageViewX.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
+
+ let widthPlay = imagePlay.image!.size.width
+ let heightPlay = imagePlay.image!.size.height
+ let xPlay = (size.width - widthPlay) / 2
+ let yPlay = (size.height - heightPlay) / 2
+ imagePlay.frame = CGRect(x: xPlay, y: yPlay, width: widthPlay, height: heightPlay)
+
+ let widthSpinner = spinner.frame.size.width
+ let heightSpinner = spinner.frame.size.height
+ let xSpinner = (size.width - widthSpinner) / 2
+ let ySpinner = (size.height - heightSpinner) / 2
+ spinner.frame = CGRect(x: xSpinner, y: ySpinner, width: widthSpinner, height: heightSpinner)
+
+ let widthManual = imageManual.image!.size.width
+ let heightManual = imageManual.image!.size.height
+ let xManual = (size.width - widthManual) / 2
+ let yManual = (size.height - heightManual) / 2
+ imageManual.frame = CGRect(x: xManual, y: yManual, width: widthManual, height: heightManual)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ let size = self.size(indexPath, messagesView: messagesView)
+ return size.height
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func size(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGSize {
+
+ return CGSize(width: RCMessages().videoBubbleWidth, height: RCMessages().videoBubbleHeight)
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/MessageCells/05_Audio/RCAudioMessageCell.swift b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/05_Audio/RCAudioMessageCell.swift
new file mode 100644
index 00000000..00344584
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/05_Audio/RCAudioMessageCell.swift
@@ -0,0 +1,130 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCAudioMessageCell: RCMessageCell {
+
+ var imageStatus: UIImageView!
+ var labelDuration: UILabel!
+ var imageManual: UIImageView!
+ var spinner: UIActivityIndicatorView!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ super.bindData(indexPath, messagesView: messagesView)
+
+ viewBubble.backgroundColor = rcmessage.incoming ? RCMessages().audioBubbleColorIncoming : RCMessages().audioBubbleColorOutgoing
+
+ if (imageStatus == nil) {
+ imageStatus = UIImageView()
+ viewBubble.addSubview(imageStatus)
+ }
+
+ if (labelDuration == nil) {
+ labelDuration = UILabel()
+ labelDuration.font = RCMessages().audioFont
+ labelDuration.textAlignment = .right
+ viewBubble.addSubview(labelDuration)
+ }
+
+ if (spinner == nil) {
+ spinner = UIActivityIndicatorView(style: .white)
+ viewBubble.addSubview(spinner)
+ }
+
+ if (imageManual == nil) {
+ imageManual = UIImageView(image: RCMessages().audioImageManual)
+ viewBubble.addSubview(imageManual)
+ }
+
+ if (rcmessage.audio_status == RC_AUDIOSTATUS_STOPPED) { imageStatus.image = RCMessages().audioImagePlay }
+ if (rcmessage.audio_status == RC_AUDIOSTATUS_PLAYING) { imageStatus.image = RCMessages().audioImagePause }
+
+ labelDuration.textColor = rcmessage.incoming ? RCMessages().audioTextColorIncoming : RCMessages().audioTextColorOutgoing
+
+ if (rcmessage.audio_duration < 60) {
+ labelDuration.text = String(format: "0:%02ld", rcmessage.audio_duration)
+ } else {
+ labelDuration.text = String(format: "%ld:%02ld", rcmessage.audio_duration / 60, rcmessage.audio_duration % 60)
+ }
+
+ if (rcmessage.status == RC_STATUS_LOADING) {
+ imageStatus.isHidden = true
+ labelDuration.isHidden = true
+ spinner.startAnimating()
+ imageManual.isHidden = true
+ }
+
+ if (rcmessage.status == RC_STATUS_SUCCEED) {
+ imageStatus.isHidden = false
+ labelDuration.isHidden = false
+ spinner.stopAnimating()
+ imageManual.isHidden = true
+ }
+
+ if (rcmessage.status == RC_STATUS_MANUAL) {
+ imageStatus.isHidden = true
+ labelDuration.isHidden = true
+ spinner.stopAnimating()
+ imageManual.isHidden = false
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func layoutSubviews() {
+
+ let size = RCAudioMessageCell.size(indexPath, messagesView: messagesView)
+
+ super.layoutSubviews(size)
+
+ let widthStatus = imageStatus.image!.size.width
+ let heightStatus = imageStatus.image!.size.height
+ let yStatus = (size.height - heightStatus) / 2
+ imageStatus.frame = CGRect(x: 10, y: yStatus, width: widthStatus, height: heightStatus)
+
+ labelDuration.frame = CGRect(x: size.width - 100, y: 0, width: 90, height: size.height)
+
+ let widthSpinner = spinner.frame.size.width
+ let heightSpinner = spinner.frame.size.height
+ let xSpinner = (size.width - widthSpinner) / 2
+ let ySpinner = (size.height - heightSpinner) / 2
+ spinner.frame = CGRect(x: xSpinner, y: ySpinner, width: widthSpinner, height: heightSpinner)
+
+ let widthManual = imageManual.image!.size.width
+ let heightManual = imageManual.image!.size.height
+ let xManual = (size.width - widthManual) / 2
+ let yManual = (size.height - heightManual) / 2
+ imageManual.frame = CGRect(x: xManual, y: yManual, width: widthManual, height: heightManual)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ let size = self.size(indexPath, messagesView: messagesView)
+ return size.height
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func size(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGSize {
+
+ return CGSize(width: RCMessages().audioBubbleWidht, height: RCMessages().audioBubbleHeight)
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/MessageCells/06_Location/RCLocationMessageCell.swift b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/06_Location/RCLocationMessageCell.swift
new file mode 100644
index 00000000..cb281a7b
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/06_Location/RCLocationMessageCell.swift
@@ -0,0 +1,85 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCLocationMessageCell: RCMessageCell {
+
+ var imageViewX: UIImageView!
+ var spinner: UIActivityIndicatorView!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ super.bindData(indexPath, messagesView: messagesView)
+
+ viewBubble.backgroundColor = rcmessage.incoming ? RCMessages().locationBubbleColorIncoming : RCMessages().locationBubbleColorOutgoing
+
+ if (imageViewX == nil) {
+ imageViewX = UIImageView()
+ imageViewX.layer.masksToBounds = true
+ imageViewX.layer.cornerRadius = RCMessages().bubbleRadius
+ viewBubble.addSubview(imageViewX)
+ }
+
+ if (spinner == nil) {
+ spinner = UIActivityIndicatorView(style: .white)
+ viewBubble.addSubview(spinner)
+ }
+
+ if (rcmessage.status == RC_STATUS_LOADING) {
+ imageViewX.image = nil
+ spinner.startAnimating()
+ }
+
+ if (rcmessage.status == RC_STATUS_SUCCEED) {
+ imageViewX.image = rcmessage.location_thumbnail
+ spinner.stopAnimating()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc override func layoutSubviews() {
+
+ let size = RCLocationMessageCell.size(indexPath, messagesView: messagesView)
+
+ super.layoutSubviews(size)
+
+ imageViewX.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
+
+ let widthSpinner = spinner.frame.size.width
+ let heightSpinner = spinner.frame.size.height
+ let xSpinner = (size.width - widthSpinner) / 2
+ let ySpinner = (size.height - heightSpinner) / 2
+ spinner.frame = CGRect(x: xSpinner, y: ySpinner, width: widthSpinner, height: heightSpinner)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ let size = self.size(indexPath, messagesView: messagesView)
+ return size.height
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func size(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGSize {
+
+ return CGSize(width: RCMessages().locationBubbleWidth, height: RCMessages().locationBubbleHeight)
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/MessageCells/RCMessageCell.swift b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/RCMessageCell.swift
new file mode 100644
index 00000000..e0c03c37
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/MessageCells/RCMessageCell.swift
@@ -0,0 +1,142 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCMessageCell: UITableViewCell {
+
+ var viewBubble: UIView!
+ var imageAvatar: UIImageView!
+ var labelAvatar: UILabel!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ backgroundColor = UIColor.clear
+
+ if (viewBubble == nil) {
+ viewBubble = UIView()
+ viewBubble.layer.cornerRadius = RCMessages().bubbleRadius
+ contentView.addSubview(viewBubble)
+ bubbleGestureRecognizer()
+ }
+
+ if (imageAvatar == nil) {
+ imageAvatar = UIImageView()
+ imageAvatar.layer.masksToBounds = true
+ imageAvatar.layer.cornerRadius = RCMessages().avatarDiameter / 2
+ imageAvatar.backgroundColor = RCMessages().avatarBackColor
+ imageAvatar.isUserInteractionEnabled = true
+ contentView.addSubview(imageAvatar)
+ avatarGestureRecognizer()
+ }
+ imageAvatar.image = messagesView.avatarImage(indexPath)
+
+ if (labelAvatar == nil) {
+ labelAvatar = UILabel()
+ labelAvatar.font = RCMessages().avatarFont
+ labelAvatar.textColor = RCMessages().avatarTextColor
+ labelAvatar.textAlignment = .center
+ contentView.addSubview(labelAvatar)
+ }
+ labelAvatar.text = (imageAvatar.image == nil) ? messagesView.avatarInitials(indexPath) : nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func layoutSubviews(_ size: CGSize) {
+
+ super.layoutSubviews()
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ let widthTable = messagesView.tableView.frame.size.width
+
+ let xBubble = rcmessage.incoming ? RCMessages().bubbleMarginLeft : (widthTable - RCMessages().bubbleMarginRight - size.width)
+ viewBubble.frame = CGRect(x: xBubble, y: 0, width: size.width, height: size.height)
+
+ let diameter = RCMessages().avatarDiameter
+ let xAvatar = rcmessage.incoming ? RCMessages().avatarMarginLeft : (widthTable - RCMessages().avatarMarginRight - diameter)
+ imageAvatar.frame = CGRect(x: xAvatar, y: size.height - diameter, width: diameter, height: diameter)
+ labelAvatar.frame = CGRect(x: xAvatar, y: size.height - diameter, width: diameter, height: diameter)
+ }
+
+ // MARK: - Gesture recognizer methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bubbleGestureRecognizer() {
+
+ let tapGesture = UITapGestureRecognizer(target: self, action: #selector(RCMessageCell.actionTapBubble))
+ viewBubble.addGestureRecognizer(tapGesture)
+ tapGesture.cancelsTouchesInView = false
+
+ let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(actionLongBubble(_:)))
+ viewBubble.addGestureRecognizer(longGesture)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func avatarGestureRecognizer() {
+
+ let tapGesture = UITapGestureRecognizer(target: self, action: #selector(actionTapAvatar))
+ imageAvatar.addGestureRecognizer(tapGesture)
+ tapGesture.cancelsTouchesInView = false
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionTapBubble() {
+
+ messagesView.view.endEditing(true)
+ messagesView.actionTapBubble(indexPath)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionTapAvatar() {
+
+ messagesView.view.endEditing(true)
+ messagesView.actionTapAvatar(indexPath)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionLongBubble(_ gestureRecognizer: UILongPressGestureRecognizer) {
+
+ switch gestureRecognizer.state {
+ case .began:
+ actionMenu()
+ case .changed:
+ break
+ case .ended:
+ break
+ case .possible:
+ break
+ case .cancelled:
+ break
+ case .failed:
+ break
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionMenu() {
+
+ if (messagesView.textInput.isFirstResponder == false) {
+ let menuController = UIMenuController.shared
+ menuController.menuItems = messagesView.menuItems(indexPath) as? [UIMenuItem]
+ menuController.setTargetRect(viewBubble.frame, in: contentView)
+ menuController.setMenuVisible(true, animated: true)
+ } else {
+ messagesView.textInput.resignFirstResponder()
+ }
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Cell/StatusCell/RCStatusCell.swift b/Messenger/Vendors/RCMessageKit/Cell/StatusCell/RCStatusCell.swift
new file mode 100644
index 00000000..7fcef868
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Cell/StatusCell/RCStatusCell.swift
@@ -0,0 +1,113 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCStatusCell: UITableViewCell {
+
+ var viewBubble: UIView!
+ var textView: UITextView!
+
+ private var indexPath: IndexPath!
+ private var messagesView: RCMessagesView!
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func bindData(_ indexPath_: IndexPath, messagesView messagesView_: RCMessagesView) {
+
+ indexPath = indexPath_
+ messagesView = messagesView_
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ backgroundColor = UIColor.clear
+
+ if (viewBubble == nil) {
+ viewBubble = UIView()
+ viewBubble.backgroundColor = RCMessages().statusBubbleColor
+ viewBubble.layer.cornerRadius = RCMessages().statusBubbleRadius
+ contentView.addSubview(viewBubble)
+ bubbleGestureRecognizer()
+ }
+
+ if (textView == nil) {
+ textView = UITextView()
+ textView.font = RCMessages().statusFont
+ textView.textColor = RCMessages().statusTextColor
+ textView.isEditable = false
+ textView.isSelectable = false
+ textView.isScrollEnabled = false
+ textView.isUserInteractionEnabled = false
+ textView.backgroundColor = UIColor.clear
+ textView.textContainer.lineFragmentPadding = 0
+ textView.textContainerInset = RCMessages().statusInset
+ viewBubble.addSubview(textView)
+ }
+
+ textView.text = rcmessage.text
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func layoutSubviews() {
+
+ super.layoutSubviews()
+
+ let widthTable = messagesView.tableView.frame.size.width
+
+ let size = RCStatusCell.size(indexPath, messagesView: messagesView)
+
+ let yBubble = RCMessages().sectionHeaderMargin
+ let xBubble = (widthTable - size.width) / 2
+ viewBubble.frame = CGRect(x: xBubble, y: yBubble, width: size.width, height: size.height)
+
+ textView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
+ }
+
+ // MARK: - Size methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc class func height(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGFloat {
+
+ let size = self.size(indexPath, messagesView: messagesView)
+ return size.height
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func size(_ indexPath: IndexPath, messagesView: RCMessagesView) -> CGSize {
+
+ let rcmessage = messagesView.rcmessage(indexPath)
+
+ let widthTable = messagesView.tableView.frame.size.width
+
+ let maxwidth = (0.95 * widthTable) - RCMessages().statusInsetLeft - RCMessages().statusInsetRight
+
+ let rect = rcmessage.text.boundingRect(with: CGSize(width: maxwidth, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: RCMessages().statusFont], context: nil)
+
+ let width = rect.size.width + RCMessages().statusInsetLeft + RCMessages().statusInsetRight
+ let height = rect.size.height + RCMessages().statusInsetTop + RCMessages().statusInsetBottom
+
+ return CGSize(width: width, height: height)
+ }
+
+ // MARK: - Gesture recognizer methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func bubbleGestureRecognizer() {
+
+ let tapGesture = UITapGestureRecognizer(target: self, action: #selector(RCStatusCell.actionTapBubble))
+ viewBubble.addGestureRecognizer(tapGesture)
+ tapGesture.cancelsTouchesInView = false
+ }
+
+ // MARK: - User actions
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func actionTapBubble() {
+
+ messagesView.view.endEditing(true)
+ messagesView.actionTapBubble(indexPath)
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/Data/RCMessage.swift b/Messenger/Vendors/RCMessageKit/Data/RCMessage.swift
new file mode 100644
index 00000000..6350e83e
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/Data/RCMessage.swift
@@ -0,0 +1,180 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import CoreLocation
+import MapKit
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc class RCMessage: NSObject {
+
+ var type: Int = 0
+
+ var incoming = false
+ var outgoing = false
+
+ var text = ""
+
+ var picture_image: UIImage?
+ var picture_width: Int = 0
+ var picture_height: Int = 0
+
+ var video_path = ""
+ var video_thumbnail: UIImage?
+ var video_duration: Int = 0
+
+ var audio_path = ""
+ var audio_duration: Int = 0
+ var audio_status: Int = 0
+
+ var latitude: CLLocationDegrees = 0
+ var longitude: CLLocationDegrees = 0
+ var location_thumbnail: UIImage?
+
+ var status: Int = 0
+
+ // MARK: - Initialization methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init() {
+
+ super.init()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ init(status text: String) {
+
+ super.init()
+
+ type = Int(RC_TYPE_STATUS)
+
+ incoming = false
+ outgoing = false
+
+ self.text = text
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ init(text: String, incoming incoming_: Bool) {
+
+ super.init()
+
+ type = Int(RC_TYPE_TEXT)
+
+ incoming = incoming_
+ outgoing = !incoming
+
+ self.text = text
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ init(emoji text: String, incoming incoming_: Bool) {
+
+ super.init()
+
+ type = Int(RC_TYPE_EMOJI)
+
+ incoming = incoming_
+ outgoing = !incoming
+
+ self.text = text
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ init(picture image: UIImage?, width: Int, height: Int, incoming incoming_: Bool) {
+
+ super.init()
+
+ type = Int(RC_TYPE_PICTURE)
+
+ incoming = incoming_
+ outgoing = !incoming
+
+ picture_image = image
+ picture_width = width
+ picture_height = height
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ init(video path: String?, duration: Int, incoming incoming_: Bool) {
+
+ super.init()
+
+ type = Int(RC_TYPE_VIDEO)
+
+ incoming = incoming_
+ outgoing = !incoming
+
+ video_path = path ?? ""
+ video_duration = duration
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ init(audio path: String?, duration: Int, incoming incoming_: Bool) {
+
+ super.init()
+
+ type = Int(RC_TYPE_AUDIO)
+
+ incoming = incoming_
+ outgoing = !incoming
+
+ audio_path = path ?? ""
+ audio_duration = duration
+ audio_status = Int(RC_AUDIOSTATUS_STOPPED)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ init(latitude: CLLocationDegrees, longitude: CLLocationDegrees, incoming incoming_: Bool, completion: @escaping () -> Void) {
+
+ super.init()
+
+ type = Int(RC_TYPE_LOCATION)
+
+ incoming = incoming_
+ outgoing = !incoming
+
+ self.latitude = latitude
+ self.longitude = longitude
+
+ status = Int(RC_STATUS_LOADING)
+
+ var region: MKCoordinateRegion = MKCoordinateRegion()
+ region.center.latitude = self.latitude
+ region.center.longitude = self.longitude
+ region.span.latitudeDelta = CLLocationDegrees(0.005)
+ region.span.longitudeDelta = CLLocationDegrees(0.005)
+
+ let options = MKMapSnapshotter.Options()
+ options.region = region
+ options.size = CGSize(width: RCMessages().locationBubbleWidth, height: RCMessages().locationBubbleHeight)
+ options.scale = UIScreen.main.scale
+
+ let snapshotter = MKMapSnapshotter(options: options)
+ snapshotter.start(with: DispatchQueue.global(qos: .default), completionHandler: { snapshot, error in
+ if (snapshot != nil) {
+ DispatchQueue.main.async {
+ UIGraphicsBeginImageContextWithOptions(snapshot!.image.size, true, snapshot!.image.scale)
+ do {
+ snapshot!.image.draw(at: CGPoint.zero)
+ let pin = MKPinAnnotationView(annotation: nil, reuseIdentifier: nil)
+ var point = snapshot!.point(for: CLLocationCoordinate2DMake(self.latitude, self.longitude))
+ point.x += pin.centerOffset.x - (pin.bounds.size.width / 2)
+ point.y += pin.centerOffset.y - (pin.bounds.size.height / 2)
+ pin.image!.draw(at: point)
+ self.location_thumbnail = UIGraphicsGetImageFromCurrentImageContext()
+ }
+ UIGraphicsEndImageContext()
+ self.status = Int(RC_STATUS_SUCCEED)
+ completion()
+ }
+ }
+ })
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/MenuItem/RCMenuItem.swift b/Messenger/Vendors/RCMessageKit/MenuItem/RCMenuItem.swift
new file mode 100644
index 00000000..9e44e90e
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/MenuItem/RCMenuItem.swift
@@ -0,0 +1,24 @@
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import UIKit
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCMenuItem: UIMenuItem {
+
+ var indexPath: IndexPath?
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ class func indexPath(_ menuController: UIMenuController) -> IndexPath? {
+
+ let menuItem = menuController.menuItems?.first as? RCMenuItem
+ return menuItem?.indexPath
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/RCMessages.swift b/Messenger/Vendors/RCMessageKit/RCMessages.swift
new file mode 100644
index 00000000..6e0edec8
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/RCMessages.swift
@@ -0,0 +1,196 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import AVFoundation
+import CoreLocation
+import MapKit
+import UIKit
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+@objc class RCMessages: NSObject {
+
+ // Section
+ //-----------------------------------------------------------------------------
+ var sectionHeaderMargin: CGFloat = 8.0
+
+ var sectionHeaderHeight: CGFloat = 20.0
+ var sectionHeaderLeft: CGFloat = 10.0
+ var sectionHeaderRight: CGFloat = 10.0
+
+ var sectionHeaderColor: UIColor = UIColor.lightGray
+ var sectionHeaderFont: UIFont = UIFont.systemFont(ofSize: 12)
+
+ var sectionFooterHeight: CGFloat = 15.0
+ var sectionFooterLeft: CGFloat = 10.0
+ var sectionFooterRight: CGFloat = 10.0
+
+ var sectionFooterColor: UIColor = UIColor.lightGray
+ var sectionFooterFont: UIFont = UIFont.systemFont(ofSize: 12)
+
+ var sectionFooterMargin: CGFloat = 8.0
+
+ // Bubble
+ //-----------------------------------------------------------------------------
+ var bubbleHeaderHeight: CGFloat = 15.0
+ var bubbleHeaderLeft: CGFloat = 50.0
+ var bubbleHeaderRight: CGFloat = 50.0
+ var bubbleHeaderColor: UIColor = UIColor.lightGray
+ var bubbleHeaderFont: UIFont = UIFont.systemFont(ofSize: 12)
+
+ var bubbleMarginLeft: CGFloat = 40.0
+ var bubbleMarginRight: CGFloat = 40.0
+ var bubbleRadius: CGFloat = 15.0
+
+ var bubbleFooterHeight: CGFloat = 15.0
+ var bubbleFooterLeft: CGFloat = 50.0
+ var bubbleFooterRight: CGFloat = 50.0
+ var bubbleFooterColor: UIColor = UIColor.lightGray
+ var bubbleFooterFont: UIFont = UIFont.systemFont(ofSize: 12)
+
+ // Avatar
+ //-----------------------------------------------------------------------------
+ var avatarDiameter: CGFloat = 30.0
+ var avatarMarginLeft: CGFloat = 5.0
+ var avatarMarginRight: CGFloat = 5.0
+
+ var avatarBackColor: UIColor = UIColor.groupTableViewBackground
+ var avatarTextColor: UIColor = UIColor.white
+
+ var avatarFont: UIFont = UIFont.systemFont(ofSize: 12)
+
+ // Status cell
+ //-----------------------------------------------------------------------------
+ var statusBubbleRadius: CGFloat = 10.0
+
+ var statusBubbleColor: UIColor = UIColor.lightText
+ var statusTextColor: UIColor = UIColor.white
+
+ var statusFont: UIFont = UIFont.systemFont(ofSize: 12, weight: .semibold)
+
+ var statusInsetLeft: CGFloat = 10.0
+ var statusInsetRight: CGFloat = 10.0
+ var statusInsetTop: CGFloat = 5.0
+ var statusInsetBottom: CGFloat = 5.0
+ var statusInset: UIEdgeInsets = UIEdgeInsets.init(top: 5.0, left: 10.0, bottom: 5.0, right: 10.0)
+
+ // Text cell
+ //-----------------------------------------------------------------------------
+ var textBubbleWidthMin: CGFloat = 45.0
+ var textBubbleHeightMin: CGFloat = 35.0
+
+ var textBubbleColorOutgoing: UIColor = UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1.0)
+ var textBubbleColorIncoming: UIColor = UIColor(red: 230/255, green: 229/255, blue: 234/255, alpha: 1.0)
+ var textTextColorOutgoing: UIColor = UIColor.white
+ var textTextColorIncoming: UIColor = UIColor.black
+
+ var textFont: UIFont = UIFont.systemFont(ofSize: 16)
+
+ var textInsetLeft: CGFloat = 10.0
+ var textInsetRight: CGFloat = 10.0
+ var textInsetTop: CGFloat = 10.0
+ var textInsetBottom: CGFloat = 10.0
+ var textInset: UIEdgeInsets = UIEdgeInsets.init(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
+
+ // Emoji cell
+ //-----------------------------------------------------------------------------
+ var emojiBubbleWidthMin: CGFloat = 45.0
+ var emojiBubbleHeightMin: CGFloat = 30.0
+
+ var emojiBubbleColorOutgoing: UIColor = UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1.0)
+ var emojiBubbleColorIncoming: UIColor = UIColor(red: 230/255, green: 229/255, blue: 234/255, alpha: 1.0)
+
+ var emojiFont: UIFont = UIFont.systemFont(ofSize: 46)
+
+ var emojiInsetLeft: CGFloat = 10.0
+ var emojiInsetRight: CGFloat = 10.0
+ var emojiInsetTop: CGFloat = 10.0
+ var emojiInsetBottom: CGFloat = 10.0
+ var emojiInset: UIEdgeInsets = UIEdgeInsets.init(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
+
+ // Picture cell
+ //-----------------------------------------------------------------------------
+ var pictureBubbleWidth: CGFloat = 200.0
+
+ var pictureBubbleColorOutgoing: UIColor = UIColor(red: 230/255, green: 229/255, blue: 234/255, alpha: 1.0)
+ var pictureBubbleColorIncoming: UIColor = UIColor(red: 230/255, green: 229/255, blue: 234/255, alpha: 1.0)
+
+ var pictureImageManual: UIImage = UIImage(named: "rcmessages_manual")!
+
+ // Video cell
+ //-----------------------------------------------------------------------------
+ var videoBubbleWidth: CGFloat = 200.0
+ var videoBubbleHeight: CGFloat = 145.0
+
+ var videoBubbleColorOutgoing: UIColor = UIColor.lightGray
+ var videoBubbleColorIncoming: UIColor = UIColor.lightGray
+
+ var videoImagePlay: UIImage = UIImage(named: "rcmessages_videoplay")!
+ var videoImageManual: UIImage = UIImage(named: "rcmessages_manual")!
+
+ // Audio cell
+ //-----------------------------------------------------------------------------
+ var audioBubbleWidht: CGFloat = 150.0
+ var audioBubbleHeight: CGFloat = 40.0
+
+ var audioBubbleColorOutgoing: UIColor = UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1.0)
+ var audioBubbleColorIncoming: UIColor = UIColor(red: 230/255, green: 229/255, blue: 234/255, alpha: 1.0)
+ var audioTextColorOutgoing: UIColor = UIColor.white
+ var audioTextColorIncoming: UIColor = UIColor.black
+
+ var audioImagePlay: UIImage = UIImage(named: "rcmessages_audioplay")!
+ var audioImagePause: UIImage = UIImage(named: "rcmessages_audiopause")!
+ var audioImageManual: UIImage = UIImage(named: "rcmessages_manual")!
+
+ var audioFont: UIFont = UIFont.systemFont(ofSize: 16)
+
+ // Location cell
+ //-----------------------------------------------------------------------------
+ var locationBubbleWidth: CGFloat = 200.0
+ var locationBubbleHeight: CGFloat = 145.0
+
+ var locationBubbleColorOutgoing: UIColor = UIColor(red: 230/255, green: 229/255, blue: 234/255, alpha: 1.0)
+ var locationBubbleColorIncoming: UIColor = UIColor(red: 230/255, green: 229/255, blue: 234/255, alpha: 1.0)
+
+ // Input view
+ //-----------------------------------------------------------------------------
+ var inputViewBackColor: UIColor = UIColor.groupTableViewBackground
+ var inputTextBackColor: UIColor = UIColor.white
+ var inputTextTextColor: UIColor = UIColor.black
+
+ var inputFont: UIFont = UIFont.systemFont(ofSize: 17)
+
+ var inputViewHeightMin: CGFloat = 44.0
+ var inputTextHeightMin: CGFloat = 30.0
+ var inputTextHeightMax: CGFloat = 110.0
+
+ var inputBorderWidth: CGFloat = 1.0
+ var inputBorderColor: CGColor = UIColor.lightGray.cgColor
+
+ var inputRadius: CGFloat = 5.0
+
+ var inputInsetLeft: CGFloat = 7.0
+ var inputInsetRight: CGFloat = 7.0
+ var inputInsetTop: CGFloat = 5.0
+ var inputInsetBottom: CGFloat = 5.0
+ var inputInset: UIEdgeInsets = UIEdgeInsets.init(top: 5.0, left: 7.0, bottom: 5.0, right: 7.0)
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ static let shared: RCMessages = {
+ let instance = RCMessages()
+ return instance
+ } ()
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override init() {
+
+ super.init()
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/RCMessagesView.swift b/Messenger/Vendors/RCMessageKit/RCMessagesView.swift
new file mode 100644
index 00000000..02459661
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/RCMessagesView.swift
@@ -0,0 +1,533 @@
+//
+// Copyright (c) 2018 Related Code - http://relatedcode.com
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+class RCMessagesView: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextViewDelegate {
+
+ @IBOutlet var viewTitle: UIView!
+ @IBOutlet var labelTitle1: UILabel!
+ @IBOutlet var labelTitle2: UILabel!
+ @IBOutlet var buttonTitle: UIButton!
+ @IBOutlet var tableView: UITableView!
+ @IBOutlet var viewLoadEarlier: UIView!
+ @IBOutlet var viewTypingIndicator: UIView!
+ @IBOutlet var viewInput: UIView!
+ @IBOutlet var buttonInputAttach: UIButton!
+ @IBOutlet var textInput: UITextView!
+ @IBOutlet var buttonInputSend: UIButton!
+
+ private var initialized = false
+ private var centerView = CGPoint.zero
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ convenience init() {
+
+ self.init(nibName: "RCMessagesView", bundle: nil)
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLoad() {
+
+ super.viewDidLoad()
+
+ navigationItem.titleView = viewTitle
+
+ tableView.register(RCSectionHeaderCell.self, forCellReuseIdentifier: "RCSectionHeaderCell")
+ tableView.register(RCBubbleHeaderCell.self, forCellReuseIdentifier: "RCBubbleHeaderCell")
+ tableView.register(RCBubbleFooterCell.self, forCellReuseIdentifier: "RCBubbleFooterCell")
+ tableView.register(RCSectionFooterCell.self, forCellReuseIdentifier: "RCSectionFooterCell")
+
+ tableView.register(RCTextMessageCell.self, forCellReuseIdentifier: "RCTextMessageCell")
+ tableView.register(RCEmojiMessageCell.self, forCellReuseIdentifier: "RCEmojiMessageCell")
+ tableView.register(RCPictureMessageCell.self, forCellReuseIdentifier: "RCPictureMessageCell")
+ tableView.register(RCVideoMessageCell.self, forCellReuseIdentifier: "RCVideoMessageCell")
+ tableView.register(RCAudioMessageCell.self, forCellReuseIdentifier: "RCAudioMessageCell")
+ tableView.register(RCLocationMessageCell.self, forCellReuseIdentifier: "RCLocationMessageCell")
+
+ tableView.register(RCStatusCell.self, forCellReuseIdentifier: "RCStatusCell")
+
+ tableView.tableHeaderView = viewLoadEarlier
+
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardShow(_:)), name: UIResponder.keyboardDidShowNotification, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil)
+
+ inputPanelInit()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidLayoutSubviews() {
+
+ super.viewDidLayoutSubviews()
+
+ centerView = view.center
+
+ inputPanelUpdate()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillAppear(_ animated: Bool) {
+
+ super.viewWillAppear(animated)
+
+ dismissKeyboard()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewDidAppear(_ animated: Bool) {
+
+ super.viewDidAppear(animated)
+
+ if (initialized == false) {
+ initialized = true
+ scroll(toBottom: true)
+ }
+
+ centerView = view.center
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func viewWillDisappear(_ animated: Bool) {
+
+ super.viewWillDisappear(animated)
+
+ dismissKeyboard()
+ }
+
+ // MARK: - Load earlier methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func loadEarlierShow(_ show: Bool) {
+
+ viewLoadEarlier.isHidden = !show
+ var frame: CGRect = viewLoadEarlier.frame
+ frame.size.height = show ? 50 : 0
+ viewLoadEarlier.frame = frame
+ tableView.reloadData()
+ }
+
+ // MARK: - Message methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func rcmessage(_ indexPath: IndexPath) -> RCMessage {
+
+ return RCMessage()
+ }
+
+ // MARK: - Avatar methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func avatarInitials(_ indexPath: IndexPath) -> String {
+
+ return ""
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func avatarImage(_ indexPath: IndexPath) -> UIImage? {
+
+ return nil
+ }
+
+ // MARK: - Header, Footer methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func textSectionHeader(_ indexPath: IndexPath) -> String? {
+
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func textBubbleHeader(_ indexPath: IndexPath) -> String? {
+
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func textBubbleFooter(_ indexPath: IndexPath) -> String? {
+
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func textSectionFooter(_ indexPath: IndexPath) -> String? {
+
+ return nil
+ }
+
+ // MARK: - Menu controller methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func menuItems(_ indexPath: IndexPath) -> [Any]? {
+
+ return nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
+
+ return false
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ override var canBecomeFirstResponder: Bool {
+
+ return true
+ }
+
+ // MARK: - Typing indicator methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func typingIndicatorShow(_ show: Bool, animated: Bool) {
+
+ if show {
+ tableView.tableFooterView = viewTypingIndicator
+ scroll(toBottom: animated)
+ } else {
+ UIView.animate(withDuration: animated ? 0.25 : 0, animations: {
+ self.tableView.tableFooterView = nil
+ })
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func typingIndicatorUpdate() {
+
+ }
+
+ // MARK: - Keyboard methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func keyboardShow(_ notification: Notification?) {
+
+ if let info = notification?.userInfo {
+ if let keyboard = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
+ let duration = TimeInterval(info[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double)
+ UIView.animate(withDuration: duration, delay: 0, options: .allowUserInteraction, animations: {
+ self.view.center = CGPoint(x: self.centerView.x, y: self.centerView.y - keyboard.size.height + self.view.safeAreaInsets.bottom)
+ })
+ }
+ }
+ UIMenuController.shared.menuItems = nil
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @objc func keyboardHide(_ notification: Notification?) {
+
+ if let info = notification?.userInfo {
+ let duration = TimeInterval(info[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double)
+ UIView.animate(withDuration: duration, delay: 0, options: .allowUserInteraction, animations: {
+ self.view.center = self.centerView
+ })
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func dismissKeyboard() {
+
+ view.endEditing(true)
+ }
+
+ // MARK: - Input panel methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func inputPanelInit() {
+
+ viewInput.backgroundColor = RCMessages().inputViewBackColor
+ textInput.backgroundColor = RCMessages().inputTextBackColor
+
+ textInput.font = RCMessages().inputFont
+ textInput.textColor = RCMessages().inputTextTextColor
+
+ textInput.textContainer.lineFragmentPadding = 0
+ textInput.textContainerInset = RCMessages().inputInset
+
+ textInput.layer.borderColor = RCMessages().inputBorderColor
+ textInput.layer.borderWidth = RCMessages().inputBorderWidth
+
+ textInput.layer.cornerRadius = RCMessages().inputRadius
+ textInput.clipsToBounds = true
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func inputPanelUpdate() {
+
+ let heightView: CGFloat = view.frame.size.height
+ let widthView: CGFloat = view.frame.size.width
+
+ let leftSafe: CGFloat = view.safeAreaInsets.left
+ let rightSafe: CGFloat = view.safeAreaInsets.right
+ let bottomSafe: CGFloat = view.safeAreaInsets.bottom
+
+ let widthText: CGFloat = textInput.frame.size.width
+ var heightText: CGFloat
+ let sizeText = textInput.sizeThatFits(CGSize(width: widthText, height: CGFloat.greatestFiniteMagnitude))
+
+ heightText = CGFloat.maximum(RCMessages().inputTextHeightMin, sizeText.height)
+ heightText = CGFloat.minimum(RCMessages().inputTextHeightMax, heightText)
+
+ let heightInput: CGFloat = heightText + (RCMessages().inputViewHeightMin - RCMessages().inputTextHeightMin)
+
+ tableView.frame = CGRect(x: leftSafe, y: 0, width: widthView - leftSafe - rightSafe, height: heightView - bottomSafe - heightInput)
+
+ var frameViewInput: CGRect = viewInput.frame
+ frameViewInput.origin.y = heightView - bottomSafe - heightInput
+ frameViewInput.size.height = heightInput
+ viewInput.frame = frameViewInput
+
+ viewInput.layoutIfNeeded()
+
+ var frameAttach: CGRect = buttonInputAttach.frame
+ frameAttach.origin.y = heightInput - frameAttach.size.height
+ buttonInputAttach.frame = frameAttach
+
+ var frameTextInput: CGRect = textInput.frame
+ frameTextInput.size.height = heightText
+ textInput.frame = frameTextInput
+
+ var frameSend: CGRect = buttonInputSend.frame
+ frameSend.origin.y = heightInput - frameSend.size.height
+ buttonInputSend.frame = frameSend
+
+ buttonInputSend.isEnabled = textInput.text.count != 0
+
+ let offset = CGPoint(x: 0, y: sizeText.height - heightText)
+ textInput.setContentOffset(offset, animated: false)
+
+ scroll(toBottom: false)
+ }
+
+ // MARK: - User actions (title)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionTitle(_ sender: Any) {
+
+ actionTitle()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionTitle() {
+
+ }
+
+ // MARK: - User actions (load earlier)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionLoadEarlier(_ sender: Any) {
+
+ actionLoadEarlier()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionLoadEarlier() {
+
+ }
+
+ // MARK: - User actions (bubble tap)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionTapBubble(_ indexPath: IndexPath) {
+
+ }
+
+ // MARK: - User actions (avatar tap)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionTapAvatar(_ indexPath: IndexPath) {
+
+ }
+
+ // MARK: - User actions (input panel)
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionInputAttach(_ sender: Any) {
+
+ actionAttachMessage()
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ @IBAction func actionInputSend(_ sender: Any) {
+
+ if (textInput.text.count != 0) {
+ actionSendMessage(textInput.text)
+ dismissKeyboard()
+ textInput.text = nil
+ inputPanelUpdate()
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionAttachMessage() {
+
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func actionSendMessage(_ text: String) {
+
+ }
+
+ // MARK: - UIScrollViewDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+
+ dismissKeyboard()
+ }
+
+ // MARK: - Table view data source
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return 5
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+
+ return RCMessages().sectionHeaderMargin
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
+
+ return RCMessages().sectionFooterMargin
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
+
+ view.tintColor = UIColor.clear
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) {
+
+ view.tintColor = UIColor.clear
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+
+ if (indexPath.row == 0) {
+ return RCSectionHeaderCell.height(indexPath, messagesView: self)
+ }
+
+ if (indexPath.row == 1) {
+ return RCBubbleHeaderCell.height(indexPath, messagesView: self)
+ }
+
+ if (indexPath.row == 2) {
+
+ let rcmessage = self.rcmessage(indexPath)
+ if (rcmessage.type == RC_TYPE_STATUS) { return RCStatusCell.height(indexPath, messagesView: self) }
+ if (rcmessage.type == RC_TYPE_TEXT) { return RCTextMessageCell.height(indexPath, messagesView: self) }
+ if (rcmessage.type == RC_TYPE_EMOJI) { return RCEmojiMessageCell.height(indexPath, messagesView: self) }
+ if (rcmessage.type == RC_TYPE_PICTURE) { return RCPictureMessageCell.height(indexPath, messagesView: self) }
+ if (rcmessage.type == RC_TYPE_VIDEO) { return RCVideoMessageCell.height(indexPath, messagesView: self) }
+ if (rcmessage.type == RC_TYPE_AUDIO) { return RCAudioMessageCell.height(indexPath, messagesView: self) }
+ if (rcmessage.type == RC_TYPE_LOCATION) { return RCLocationMessageCell.height(indexPath, messagesView: self) }
+ }
+
+ if (indexPath.row == 3) {
+ return RCBubbleFooterCell.height(indexPath, messagesView: self)
+ }
+
+ if (indexPath.row == 4) {
+ return RCSectionFooterCell.height(indexPath, messagesView: self)
+ }
+ return 0
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ if (indexPath.row == 0) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCSectionHeaderCell", for: indexPath) as! RCSectionHeaderCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+
+ if (indexPath.row == 1) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCBubbleHeaderCell", for: indexPath) as! RCBubbleHeaderCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+
+ if (indexPath.row == 2) {
+ let rcmessage = self.rcmessage(indexPath)
+ if (rcmessage.type == RC_TYPE_STATUS) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCStatusCell", for: indexPath) as! RCStatusCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+ if (rcmessage.type == RC_TYPE_TEXT) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCTextMessageCell", for: indexPath) as! RCTextMessageCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+ if (rcmessage.type == RC_TYPE_EMOJI) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCEmojiMessageCell", for: indexPath) as! RCEmojiMessageCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+ if (rcmessage.type == RC_TYPE_PICTURE) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCPictureMessageCell", for: indexPath) as! RCPictureMessageCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+ if (rcmessage.type == RC_TYPE_VIDEO) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCVideoMessageCell", for: indexPath) as! RCVideoMessageCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+ if (rcmessage.type == RC_TYPE_AUDIO) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCAudioMessageCell", for: indexPath) as! RCAudioMessageCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+ if (rcmessage.type == RC_TYPE_LOCATION) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCLocationMessageCell", for: indexPath) as! RCLocationMessageCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+ }
+
+ if (indexPath.row == 3) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCBubbleFooterCell", for: indexPath) as! RCBubbleFooterCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+
+ if (indexPath.row == 4) {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RCSectionFooterCell", for: indexPath) as! RCSectionFooterCell
+ cell.bindData(indexPath, messagesView: self)
+ return cell
+ }
+
+ return UITableViewCell()
+ }
+
+ // MARK: - Helper methods
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func scroll(toBottom animated: Bool) {
+
+ if (tableView.numberOfSections > 0) {
+ let indexPath = IndexPath(row: 0, section: tableView.numberOfSections - 1)
+ tableView.scrollToRow(at: indexPath, at: .top, animated: animated)
+ }
+ }
+
+ // MARK: - UITextViewDelegate
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
+
+ return true
+ }
+
+ //---------------------------------------------------------------------------------------------------------------------------------------------
+ func textViewDidChange(_ textView: UITextView) {
+
+ inputPanelUpdate()
+ typingIndicatorUpdate()
+ }
+}
diff --git a/Messenger/Vendors/RCMessageKit/RCMessagesView.xib b/Messenger/Vendors/RCMessageKit/RCMessagesView.xib
new file mode 100644
index 00000000..a36b2b92
--- /dev/null
+++ b/Messenger/Vendors/RCMessageKit/RCMessagesView.xib
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Messenger/Vendors/RCMessageKit/Resources/rcmessage_attach@2x.png b/Messenger/Vendors/RCMessageKit/Resources/rcmessage_attach@2x.png
new file mode 100644
index 00000000..59d4cfa7
Binary files /dev/null and b/Messenger/Vendors/RCMessageKit/Resources/rcmessage_attach@2x.png differ
diff --git a/Messenger/Vendors/RCMessageKit/Resources/rcmessage_incoming.aiff b/Messenger/Vendors/RCMessageKit/Resources/rcmessage_incoming.aiff
new file mode 100644
index 00000000..ab73ce2a
Binary files /dev/null and b/Messenger/Vendors/RCMessageKit/Resources/rcmessage_incoming.aiff differ
diff --git a/Messenger/Vendors/RCMessageKit/Resources/rcmessage_outgoing.aiff b/Messenger/Vendors/RCMessageKit/Resources/rcmessage_outgoing.aiff
new file mode 100644
index 00000000..e46adff5
Binary files /dev/null and b/Messenger/Vendors/RCMessageKit/Resources/rcmessage_outgoing.aiff differ
diff --git a/Messenger/Vendors/RCMessageKit/Resources/rcmessage_send@2x.png b/Messenger/Vendors/RCMessageKit/Resources/rcmessage_send@2x.png
new file mode 100644
index 00000000..81df8844
Binary files /dev/null and b/Messenger/Vendors/RCMessageKit/Resources/rcmessage_send@2x.png differ
diff --git a/Messenger/Vendors/RCMessageKit/Resources/rcmessages_audiopause@2x.png b/Messenger/Vendors/RCMessageKit/Resources/rcmessages_audiopause@2x.png
new file mode 100644
index 00000000..bf6039e0
Binary files /dev/null and b/Messenger/Vendors/RCMessageKit/Resources/rcmessages_audiopause@2x.png differ
diff --git a/Messenger/Vendors/RCMessageKit/Resources/rcmessages_audioplay@2x.png b/Messenger/Vendors/RCMessageKit/Resources/rcmessages_audioplay@2x.png
new file mode 100644
index 00000000..8e4154c1
Binary files /dev/null and b/Messenger/Vendors/RCMessageKit/Resources/rcmessages_audioplay@2x.png differ
diff --git a/Messenger/Vendors/RCMessageKit/Resources/rcmessages_manual@2x.png b/Messenger/Vendors/RCMessageKit/Resources/rcmessages_manual@2x.png
new file mode 100644
index 00000000..ddb62fcb
Binary files /dev/null and b/Messenger/Vendors/RCMessageKit/Resources/rcmessages_manual@2x.png differ
diff --git a/Messenger/Vendors/RCMessageKit/Resources/rcmessages_videoplay@2x.png b/Messenger/Vendors/RCMessageKit/Resources/rcmessages_videoplay@2x.png
new file mode 100644
index 00000000..f29dca82
Binary files /dev/null and b/Messenger/Vendors/RCMessageKit/Resources/rcmessages_videoplay@2x.png differ
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINAPSEnvironment.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINAPSEnvironment.h
new file mode 100644
index 00000000..ff1bd7ec
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINAPSEnvironment.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#ifndef SIN_APS_ENVIRONMENT_H
+#define SIN_APS_ENVIRONMENT_H
+
+/**
+ * SINAPSEnvironment is used to declare to which Apple Push Notification Service environment a device token is bound to.
+ *
+ * SINAPSEnvironment is used with `-[SINClient registerPushNotificationDeviceToken:type:apsEnvironment:]` or
+ * `SINManagedPush`.
+ *
+ * ### Example
+ *
+ * An application which is codesigned and provisioned with a "Development" Provisioning Profile
+ * will be tied to the APNS Development Gateway (gateway.sandbox.push.apple.com)
+ *
+ * An application which is codesigned and provisioned with a "Distribution" Provisioning Profile
+ * will be tied to the APNS Production Gateway (gateway.push.apple.com)
+ *
+ * The macro `SINAPSEnvironmentAutomatic` can be used to specify SINAPSEnvironment based on the type of build.
+ * (Because it is a pre-processor macro, it will be based on build configuration (Debug/Release) of the application
+ * which is consuming the Sinch SDK.)
+ *
+ * See Apple documentation for further details:
+ * https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ProvisioningDevelopment.html
+ */
+
+typedef NS_ENUM(NSInteger, SINAPSEnvironment) {
+ SINAPSEnvironmentDevelopment = 1, // APNS Development environment
+ SINAPSEnvironmentProduction = 2 // APNS Production environment
+};
+
+// The following defines SINAPSEnvironmentAutomatic based on presence
+// of the pre-processing macros NDEBUG and/or DEBUG.
+// If NDEBUG is defined it will have precedence over DEBUG.
+
+#ifndef SINAPSEnvironmentAutomatic
+#ifdef NDEBUG
+#define SINAPSEnvironmentAutomatic SINAPSEnvironmentProduction
+#else
+#ifdef DEBUG
+#define SINAPSEnvironmentAutomatic SINAPSEnvironmentDevelopment
+#else
+#define SINAPSEnvironmentAutomatic SINAPSEnvironmentProduction
+#endif // ifdef DEBUG
+#endif // ifdef NDEBUG
+#endif // ifndef SINAPSEnvironmentAutomatic
+
+#endif // SIN_APS_ENVIRONMENT_H
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINAudioController.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINAudioController.h
new file mode 100644
index 00000000..34016d56
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINAudioController.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+@protocol SINAudioControllerDelegate;
+
+#pragma mark - SINAudioController
+
+/**
+ * The SINAudioController provides methods for controlling audio related
+ * functionality, e.g. enabling the speaker, muting the microphone, and
+ * playing sound files.
+ *
+ * ### Playing Sound Files
+ *
+ * The audio controller provides a convenience method
+ * (startPlayingSoundFile:loop:) for playing sounds
+ * that are related to a call, such as ringtones and busy tones.
+ *
+ * ### Example
+ *
+ * id audio = [client audioController];
+ * NSString *soundPath = [[NSBundle mainBundle] pathForResource:@"ringtone"
+ * ofType:@"wav"];
+ *
+ * [audio startPlayingSoundFile:soundPath loop:YES];
+ *
+ *
+ * Applications that prefer to use their own code for playing sounds are free
+ * to do so, but they should follow a few guidelines related to audio
+ * session categories and audio session activation/deactivation (see
+ * Sinch SDK User Guide for details).
+ *
+ * #### Sound File Format
+ *
+ * The sound file must be a mono (1 channel), 16-bit, uncompressed (PCM)
+ * .wav file with a sample rate of 8kHz, 16kHz, or 32kHz.
+ */
+@protocol SINAudioController
+
+/**
+ * The object that acts as the delegate of the audio controller.
+ *
+ * The delegate object handles audio related state changes.
+ *
+ * @see SINAudioControllerDelegate
+ */
+@property (nonatomic, weak) id delegate;
+
+/**
+ * Mute the microphone.
+ */
+- (void)mute;
+
+/**
+ * Unmute the microphone.
+ */
+- (void)unmute;
+
+/**
+ * Route the call audio through the speaker.
+ *
+ * Changing the audio route for a call is only possible when the call has
+ * been established.
+ *
+ * @see SINCallStateEstablished
+ * @see -[SINCallDelegate callDidEstablish:]
+ *
+ */
+- (void)enableSpeaker;
+
+/**
+ * Route the call audio through the handset earpiece.
+ *
+ * Changing the audio route for a call is only possible when the call has
+ * been established.
+ *
+ * @see SINCallStateEstablished
+ * @see -[SINCallDelegate callDidEstablish:]
+ *
+ */
+- (void)disableSpeaker;
+
+/**
+ * Play a sound file, for the purpose of playing ringtones, etc.
+ *
+ * This is a simple convenience method for playing sounds associated with
+ * a call, such as ringtones. It can only play one sound file at a time.
+ *
+ * For advanced audio, apps that use the SDK should implement their own
+ * methods for playing sounds.
+ *
+ * Regardless of whether a sound is looping or not, a corresponding call
+ * to the stopPlayingSoundFile method must be done at some point after each
+ * invocation of this method.
+ *
+ * The sound file must be a mono (1 channel), 16-bit, uncompressed (PCM)
+ * .wav file with a sample rate of 8kHz, 16kHz, or 32kHz.
+ *
+ * @param path Full path for the sound file to play.
+ *
+ * @param loop Specifies whether the sound should loop or not.
+ *
+ * @exception NSInvalidArgumentException Throws exception if no file exists
+ * at the given path.
+ *
+ */
+- (void)startPlayingSoundFile:(NSString *)path loop:(BOOL)loop;
+
+/**
+ * Stop playing the sound file.
+ */
+- (void)stopPlayingSoundFile;
+
+@end
+
+/**
+ * The delegate of a SINAudioController object must adopt the
+ * SINAudioControllerDelegate protocol. The methods handle audio
+ * related state changes.
+ */
+@protocol SINAudioControllerDelegate
+@optional
+
+/**
+ * Notifies the delegate that the microphone was muted.
+ *
+ * @param audioController The audio controller associated with this delegate.
+ *
+ * @see SINAudioController
+ */
+- (void)audioControllerMuted:(id)audioController;
+
+/**
+ * Notifies the delegate that the microphone was unmuted.
+ *
+ * @param audioController The audio controller associated with this delegate.
+ *
+ * @see SINAudioController
+ */
+- (void)audioControllerUnmuted:(id)audioController;
+
+/**
+ * Notifies the delegate that the speaker was enabled.
+ *
+ * @param audioController The audio controller associated with this delegate.
+ *
+ * @see SINAudioController
+ */
+- (void)audioControllerSpeakerEnabled:(id)audioController;
+
+/**
+ * Notifies the delegate that the speaker was disabled.
+ *
+ * @param audioController The audio controller associated with this delegate.
+ *
+ * @see SINAudioController
+ */
+- (void)audioControllerSpeakerDisabled:(id)audioController;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCall.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCall.h
new file mode 100644
index 00000000..d0cbb666
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCall.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+@protocol SINCallDelegate;
+@protocol SINCallDetails;
+@protocol SINPushPair;
+
+#pragma mark - Call State
+
+typedef NS_ENUM(NSInteger, SINCallState) {
+ SINCallStateInitiating = 0,
+ SINCallStateProgressing, // Only applicable to outgoing calls
+ SINCallStateEstablished,
+ SINCallStateEnded
+};
+
+#pragma mark - Call Direction
+
+typedef NS_ENUM(NSInteger, SINCallDirection) { SINCallDirectionIncoming = 0, SINCallDirectionOutgoing };
+
+#pragma mark - SINCall
+
+/**
+ * The SINCall represents a call.
+ */
+@protocol SINCall
+
+/**
+ * The object that acts as the delegate of the call.
+ *
+ * The delegate object handles call state change events and must
+ * adopt the SINCallDelegate protocol.
+ *
+ * @see SINCallDelegate
+ */
+@property (nonatomic, weak) id delegate;
+
+/** String that is used as an identifier for this particular call. */
+@property (nonatomic, readonly, copy) NSString *callId;
+
+/** The id of the remote participant in the call. */
+@property (nonatomic, readonly, copy) NSString *remoteUserId;
+
+/**
+ * Metadata about a call, such as start time.
+ *
+ * When a call has ended, the details object contains information
+ * about the reason the call ended and error information if the
+ * call ended unexpectedly.
+ *
+ * @see SINCallDetails
+ */
+@property (nonatomic, readonly, strong) id details;
+
+/**
+ * The state the call is currently in. It may be one of the following:
+ *
+ * - `SINCallStateInitiating`
+ * - `SINCallStateProgressing`
+ * - `SINCallStateEstablished`
+ * - `SINCallStateEnded`
+ *
+ * Initially, the call will be in the `SINCallStateInitiating` state.
+ */
+@property (nonatomic, readonly, assign) SINCallState state;
+
+/**
+ * The direction of the call. It may be one of the following:
+ *
+ * - `SINCallDirectionIncoming`
+ * - `SINCallDirectionOutgoing`
+ *
+ */
+@property (nonatomic, readonly, assign) SINCallDirection direction;
+
+/**
+ * Call headers.
+ *
+ * Any application-defined call meta-data can be passed via headers.
+ *
+ * E.g. a human-readable "display name / username" can be convenient
+ * to send as an application-defined header.
+ *
+ * IMPORTANT: If a call is initially received via remote push
+ * notifications, headers may not be immediately available due to
+ * push payload size limitations (especially pre- iOS 8).
+ * If it's not immediately available, it will be available after the
+ * event callbacks -[SINCallDelegate callDidProgress:] or
+ * -[SINCallDelegate callDidEstablish:] .
+ *
+ **/
+@property (nonatomic, readonly) NSDictionary *headers;
+
+/**
+ * The user data property may be used to associate an arbitrary
+ * contextual object with a particular instance of a call.
+ */
+@property (nonatomic, strong) id userInfo;
+
+/** Answer an incoming call. */
+- (void)answer;
+
+/**
+ * Ends the call, regardless of what state it is in. If the call is
+ * an incoming call that has not yet been answered, the call will
+ * be reported as denied to the caller.
+ */
+- (void)hangup;
+
+/**
+ * Sends a DTMF tone for tone dialing. (Only applicable for calls terminated
+ * to PSTN (Publicly Switched Telephone Network)).
+ *
+ * @param key DTMF key must be in [0-9, #, *, A-D].
+ *
+ * @exception NSInvalidArgumentException Throws exception if key does not have a
+ * valid mapping to a DTMF tone.
+ *
+ */
+- (void)sendDTMF:(NSString *)key;
+
+/**
+ * Pause video track for this call
+ *
+ */
+- (void)pauseVideo;
+
+/**
+ * Start video track for this call
+ *
+ */
+- (void)resumeVideo;
+
+
+@end
+
+#pragma mark - SINCallDelegate
+
+/**
+ * The delegate of a SINCall object must adopt the SINCallDelegate
+ * protocol. The required methods handle call state changes.
+ *
+ * ### Call State Progression
+ *
+ * For a complete outgoing call, the delegate methods will be called
+ * in the following order:
+ *
+ * - `callDidProgress:`
+ * - `callDidEstablish:`
+ * - `callDidEnd:`
+ *
+ * For a complete incoming call, the delegate methods will be called
+ * in the following order, after the client delegate method
+ * `[SINClientDelegate client:didReceiveIncomingCall:]` has been called:
+ *
+ * - `callDidEstablish:`
+ * - `callDidEnd:`
+ */
+@protocol SINCallDelegate
+
+@optional
+
+/**
+ * Tells the delegate that the call ended.
+ *
+ * The call has entered the `SINCallStateEnded` state.
+ *
+ * @param call The call that ended.
+ *
+ * @see SINCall
+ */
+- (void)callDidEnd:(id)call;
+
+/**
+ * Tells the delegate that the outgoing call is progressing and a progress tone can be played.
+ *
+ * The call has entered the `SINCallStateProgressing` state.
+ *
+ * @param call The outgoing call to the client on the other end.
+ *
+ * @see SINCall
+ */
+- (void)callDidProgress:(id)call;
+
+/**
+ * Tells the delegate that the call was established.
+ *
+ * The call has entered the `SINCallStateEstablished` state.
+ *
+ * @param call The call that was established.
+ *
+ * @see SINCall
+ */
+- (void)callDidEstablish:(id)call;
+
+/**
+ * Tells the delegate that the callee device can't be reached directly,
+ * and it is required to wake up the callee's application with an
+ * Apple Push Notification (APN).
+ *
+ * @param call The call that requires the delegate to send an
+ * Apple Push Notification (APN) to the callee device.
+ *
+ * @param pushPairs Array of SINPushPair. Each pair identififies a certain
+ * device that should be requested to be woken up via
+ * Apple Push Notification.
+ *
+ * The push data entries are equal to what the receiver's
+ * application passed to the method
+ * -[SINClient registerPushNotificationData:].
+ *
+ * @see SINPushPair
+ * @see SINCall
+ * @see SINClient
+ */
+- (void)call:(id)call shouldSendPushNotifications:(NSArray *)pushPairs;
+
+/**
+ * Tells the delegate that a video track has been added to the call.
+ * (A delegate can use `SINVideoController` to manage rendering views.)
+ *
+ * @see SINVideoController
+ */
+- (void)callDidAddVideoTrack:(id)call;
+- (void)callDidPauseVideoTrack:(id)call;
+- (void)callDidResumeVideoTrack:(id)call;
+
+
+@end
+
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCallClient.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCallClient.h
new file mode 100644
index 00000000..b69c0233
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCallClient.h
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+#import
+#import
+
+@class SINLocalNotification;
+@class CXProvider;
+@protocol SINCall;
+@protocol SINCallClientDelegate;
+
+/**
+ * SINCallClient provides the entry point to the calling functionality of the Sinch SDK.
+ * A SINCallClient can be acquired via SINClient.
+ *
+ * ### Example
+ *
+ * id sinchClient;
+ * [sinchClient setSupportCalling:YES];
+ * [sinchClient start];
+ * ...
+ *
+ * // Place outgoing call.
+ * id callClient = [sinchClient callClient];
+ * id call = [callClient callUserWithId:@""];
+ *
+ * // Set the call delegate that handles all the call state changes
+ * call.delegate= ... ;
+ *
+ * // ...
+ *
+ * // Hang up the call
+ * [call hangup];
+ *
+ */
+
+SIN_EXPORT SIN_EXTERN NSString *const SINIncomingCallNotification; // userInfo contains SINCall
+SIN_EXPORT SIN_EXTERN NSString *const SINCallDidProgressNotification; // userInfo contains SINCall
+SIN_EXPORT SIN_EXTERN NSString *const SINCallDidEstablishNotification; // userInfo contains SINCall
+SIN_EXPORT SIN_EXTERN NSString *const SINCallDidEndNotification; // userInfo contains SINCall
+SIN_EXPORT SIN_EXTERN NSString *const SINCallKey; // SINCallKey is used for SINCall in userInfo;
+
+@protocol SINCallClient
+
+/**
+ * The object that acts as the delegate of the call client.
+ *
+ * The delegate object handles call state change events and must
+ * adopt the SINCallClientDelegate protocol.
+ *
+ * @see SINCallClientDelegate
+ */
+@property (nonatomic, weak) id delegate;
+
+/**
+ * Make a call to the user with the given id.
+ *
+ * @param userId The application specific id of the user to call.
+ *
+ * @exception NSInternalInconsistencyException Throws an exception if attempting
+ * to initiate a call before the
+ * SINClient is started.
+ * @see -[SINClientDelegate clientDidStart:].
+ * @return SINCall Outgoing call
+ */
+- (id)callUserWithId:(NSString *)userId;
+
+/**
+* Calls the user with the given id and the given headers.
+*
+* @param userId The application specific id of the user to call.
+*
+* @param headers NSString key-value pairs to pass with the call.
+* The total size of header keys + values (when encoded with NSUTF8StringEncoding)
+* must not exceed 1024 bytes.
+*
+* @exception NSInternalInconsistencyException Throws an exception if attempting
+* to initiate a call before the
+* SINClient is started.
+* @see -[SINClientDelegate clientDidStart:].
+*
+* @exception NSInvalidArgumentException Throws an exception if headers are not strictly
+* containing only keys and values that are of type NSString,
+* or if the size of all header strings exceeds 1024 bytes when
+* encoded as UTF-8.
+*
+* @return SINCall Outgoing call
+*/
+- (id)callUserWithId:(NSString *)userId headers:(NSDictionary *)headers;
+
+/**
+ * Make a video call to the user with the given id
+ *
+ * @param userId The application specific id of the user to call.
+ * @exception NSInternalInconsistencyException Throws an exception if attempting
+ * to initiate a call before the
+ * SINClient is started.
+ * @see -[SINClientDelegate clientDidStart:].
+ * @return SINCall Outgoing call
+ */
+- (id)callUserVideoWithId:(NSString *)userId;
+
+/**
+ * Make a video call to the user with the given id and the give headers
+ *
+ * @param userId The application specific id of the user to call.
+ *
+ * @param headers NSString key-value pairs to pass with the call.
+ * The total size of header keys + values (when encoded with NSUTF8StringEncoding)
+ * must not exceed 1024 bytes.
+ *
+ * @exception NSInternalInconsistencyException Throws an exception if attempting
+ * to initiate a call before the
+ * SINClient is started.
+ * @see -[SINClientDelegate clientDidStart:].
+ *
+ * @exception NSInvalidArgumentException Throws an exception if headers are not strictly
+ * containing only keys and values that are of type NSString,
+ * or if the size of all header strings exceeds 1024 bytes when
+ * encoded as UTF-8.
+ *
+ * @return SINCall Outgoing call
+ */
+- (id)callUserVideoWithId:(NSString *)userId headers:(NSDictionary *)headers;
+
+/**
+ * Calls a phone number and terminates the call to the PSTN-network (Publicly Switched
+ * Telephone Network).
+ *
+ * @param phoneNumber The phone number to call.
+ * The phone number should be given according to E.164 number formatting
+ * (http://en.wikipedia.org/wiki/E.164) and should be prefixed with a '+'.
+ * E.g. to call the US phone number 415 555 0101, it should be specified as
+ * "+14155550101", where the '+' is the required prefix and the US country
+ * code '1' added before the local subscriber number.
+ *
+ * @exception NSInternalInconsistencyException Throws an exception if attempting
+ * to initiate a call before the
+ * SINClient is started.
+ * @see -[SINClientDelegate clientDidStart:].
+ * @return SINCall Outgoing call
+ */
+- (id)callPhoneNumber:(NSString *)phoneNumber;
+
+/**
+* Calls a phone number and terminate the call to the PSTN-network (Publicly Switched
+* Telephone Network).
+*
+* @param phoneNumber The phone number to call.
+* The phone number should be given according to E.164 number formatting
+* (http://en.wikipedia.org/wiki/E.164) and should be prefixed with a '+'.
+* E.g. to call the US phone number 415 555 0101, it should be specified as
+* "+14155550101", where the '+' is the required prefix and the US country
+* code '1' added before the local subscriber number.
+*
+* @param headers NSString key-value pairs to pass with the call.
+* The total size of header keys + values (when encoded with NSUTF8StringEncoding)
+* must not exceed 1024 bytes.
+*
+* @exception NSInternalInconsistencyException Throws an exception if attempting
+* to initiate a call before the
+* SINClient is started.
+* @see -[SINClientDelegate clientDidStart:].
+*
+* @exception NSInvalidArgumentException Throws an exception if headers are not strictly
+* containing only keys and values that are of type NSString,
+* or if the size of all header strings exceeds 1024 bytes when
+* encoded as UTF-8.
+*
+* @return SINCall Outgoing call
+*/
+- (id)callPhoneNumber:(NSString *)phoneNumber headers:(NSDictionary *)headers;
+
+/**
+ * Make a SIP call to user with the given SIP Identity.
+ *
+ * @param sipIdentity The SIP identity string of the user to call, should be in the form of “user@domain”.
+ *
+ * @exception NSInternalInconsistencyException Throws an exception if attempting
+ * to initiate a call before the
+ * SINClient is started.
+ * @see -[SINClientDelegate clientDidStart:].
+ * @return SINCall Outgoing call
+ */
+- (id)callSIP:(NSString *)sipIdentity;
+
+/**
+ * Make a SIP call to user with the given SIP Identity and adding the given headers.
+ *
+ * @param sipIdentity The SIP identity string of the user to call, should be in the form of “user@domain”.
+ *
+ * @param headers NSString key-value pairs to pass with the call.
+ * The total size of header keys + values (when encoded with NSUTF8StringEncoding)
+ * must not exceed 1024 bytes.
+ *
+ * @exception NSInternalInconsistencyException Throws an exception if attempting
+ * to initiate a call before the
+ * SINClient is started.
+ * @see -[SINClientDelegate clientDidStart:].
+ * @return SINCall Outgoing call
+ */
+- (id)callSIP:(NSString *)sipIdentity headers:(NSDictionary*)headers;
+
+/**
+* Calls the conference with the given id.
+*
+* @param conferenceId The application specific id of the conference to call.
+*
+* @exception NSInternalInconsistencyException Throws an exception if attempting
+* to initiate a call before the
+* SINClient is started.
+* @see -[SINClientDelegate clientDidStart:].
+*
+* @exception NSInvalidArgumentException Throws an exception if conferenceId is longer than the maximum allowed 64
+* characters.
+* @return SINCall Outgoing call
+*/
+
+- (id)callConferenceWithId:(NSString *)conferenceId;
+
+/**
+* Calls the conference with the given id and the given headers.
+*
+* @param conferenceId The application specific id of the conference to call.
+*
+* @param headers NSString key-value pairs to pass with the call.
+* The total size of header keys + values (when encoded with NSUTF8StringEncoding)
+* must not exceed 1024 bytes.
+*
+* @exception NSInternalInconsistencyException Throws an exception if attempting
+* to initiate a call before the
+* SINClient is started.
+* @see -[SINClientDelegate clientDidStart:].
+*
+* @exception NSInvalidArgumentException Throws an exception if conferenceId is longer than the maximum allowed 64
+* characters.
+*
+* @exception NSInvalidArgumentException Throws an exception if headers are not strictly
+* containing only keys and values that are of type NSString,
+* or if the size of all header strings exceeds 1024 bytes when
+* encoded as UTF-8.
+*
+* @return SINCall Outgoing call
+*/
+- (id)callConferenceWithId:(NSString *)conferenceId headers:(NSDictionary *)headers;
+
+/**
+ * This API is introduced to support CallKit integration. Invoke this method to notify the Sinch SDK that the App has
+ * received the didActivateAudioSession callback from CXProviderDelegate. When CallKit is integrated in the App and an
+ * incoming call is received in the background, this method has to be invoked for the Sinch SDK to start the media for
+ * the call.
+ *
+ * @param audioSession The audioSession from the didActivateAudioSession callback of CXProviderDelegate.
+ */
+- (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession;
+
+@end
+
+@protocol SINCallClientDelegate
+
+@optional
+
+/**
+ * Tells the delegate that an incoming call will be received. This is specially
+ * useful for reporting the incoming call to CallKit when the app is in background.
+ *
+ * To receive further events related to this call, a SINCallDelegate
+ * should be assigned to the call.
+ *
+ * The call has entered the `SINCallStateInitiating` state.
+ *
+ * @param client The client informing the delegate that an incoming call
+ * will be received. The delegate of the incoming call object
+ * should be set by the implementation of this method.
+ *
+ * @param call The incoming call.
+ *
+ * @see SINCallClient, SINCall, SINCallDelegate
+ */
+- (void)client:(id)client willReceiveIncomingCall:(id)call;
+
+/**
+ * Tells the delegate that an incoming call has been received.
+ *
+ * To receive further events related to this call, a SINCallDelegate
+ * should be assigned to the call.
+ *
+ * The call has entered the `SINCallStateInitiating` state.
+ *
+ * @param client The client informing the delegate that an incoming call
+ * was received. The delegate of the incoming call object
+ * should be set by the implementation of this method.
+ *
+ * @param call The incoming call.
+ *
+ * @see SINCallClient, SINCall, SINCallDelegate
+ */
+- (void)client:(id)client didReceiveIncomingCall:(id)call;
+
+/**
+ * Method for providing presentation related data for a local notification used
+ * to notify the application user of an incoming call.
+ *
+ * The return value will be used by SINCallClient to schedule a
+ * 'Local Push Notification', i.e. a UILocalNotification.
+ * That UILocalNotification, when triggered and taken action upon by the user,
+ * is supposed to be used in conjunction with
+ * -[SINClient relayLocalNotification:].
+ *
+ * This method is declared as optional, but it is required to be implemented
+ * if support for receiving calls via VoIP Push Notifications (using PushKit and
+ * optionally SINManagedPush) is desired.
+ *
+ * Hanging up an incoming call while being in the background is a valid operation.
+ * This can be useful to dismiss an incoming call while the user is busy, e.g.
+ * in a regular phone call. This will effectively prevent the SDK from invoking
+ * the -[SINCallClientDelegate client:didReceiveIncomingCall:] method when the app returns to
+ * foreground.
+ * Invoking -[SINCall answer] is pended until the app returns to the foreground.
+ *
+ * @param client The client requesting a local notification
+ *
+ * @param call A SINCall object representing the incoming call.
+ *
+ * @return SINLocalNotification The delegate is responsible for composing a
+ * SINLocalNotification which can be used to
+ * present an incoming call.
+ *
+ * @see SINLocalNotification
+ * @see SINCallClient
+ * @see SINCall
+ */
+- (SINLocalNotification *)client:(id)client localNotificationForIncomingCall:(id)call;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCallDetails.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCallDetails.h
new file mode 100644
index 00000000..163d1c92
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCallDetails.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+#import
+
+#pragma mark - Call End Cause
+
+typedef NS_ENUM(NSInteger, SINCallEndCause) {
+ SINCallEndCauseNone = 0,
+ SINCallEndCauseTimeout = 1,
+ SINCallEndCauseDenied = 2,
+ SINCallEndCauseNoAnswer = 3,
+ SINCallEndCauseError = 4,
+ SINCallEndCauseHungUp = 5,
+ SINCallEndCauseCanceled = 6,
+ SINCallEndCauseOtherDeviceAnswered = 7
+};
+
+#pragma mark - SINCallDetails
+
+/**
+ * The SINCallDetails holds metadata about a call (SINCall).
+ */
+@protocol SINCallDetails
+
+/**
+ * The start time of the call.
+ *
+ * Before the call has started, the value of the startedTime property is `nil`.
+ */
+@property (nonatomic, readonly, strong) NSDate *startedTime;
+
+/**
+ * The time at which the call was established, if it reached established state.
+ *
+ * Before the call has reached established state, the value of the establishedTime property is `nil`.
+ */
+@property (nonatomic, readonly, strong) NSDate *establishedTime;
+
+/**
+ * The end time of the call.
+ *
+ * Before the call has ended, the value of the endedTime property is `nil`.
+ */
+@property (nonatomic, readonly, strong) NSDate *endedTime;
+
+/**
+ * Holds the cause of why a call ended, after it has ended. It may be one
+ * of the following:
+ *
+ * - `SINCallEndCauseNone`
+ * - `SINCallEndCauseTimeout`
+ * - `SINCallEndCauseDenied`
+ * - `SINCallEndCauseNoAnswer`
+ * - `SINCallEndCauseError`
+ * - `SINCallEndCauseHungUp`
+ * - `SINCallEndCauseCanceled`
+ * - `SINCallEndCauseOtherDeviceAnswered`
+ *
+ * If the call has not ended yet, the value is `SINCallEndCauseNone`.
+ */
+@property (nonatomic, readonly) SINCallEndCause endCause;
+
+/**
+ * If the end cause is error, then this property contains an error object
+ * that describes the error.
+ *
+ * If the call has not ended yet or if the end cause is not an error,
+ * the value of this property is `nil`.
+ */
+@property (nonatomic, readonly, strong) NSError *error;
+
+/**
+ * The application state when the call was received.
+ */
+@property (nonatomic, readonly) UIApplicationState applicationStateWhenReceived;
+
+/**
+ * Hint that indicates if video is offered in the call.
+ */
+@property (nonatomic, readonly, getter=isVideoOffered) BOOL videoOffered;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCallNotificationResult.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCallNotificationResult.h
new file mode 100644
index 00000000..250a9304
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINCallNotificationResult.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+/**
+ * SINCallNotificationResult is used to indicate the result of calling the methods
+ * -[SINClient relayLocalNotification:] and
+ * -[SINClient relayRemotePushNotificationPayload:]
+ * , when the Sinch-specific payload in the notification represents an incoming
+ * call.
+ *
+ * One example of a scenario where SINCallNotificationResult is when a user
+ * have been attempted to be reached, but not acted on the notification directly.
+ * In that case, the notification result object can indicate that the
+ * notification is too old (`isTimedOut`), and also contains the `remoteUserId`
+ * which can be used for display purposes.
+ **/
+
+@protocol SINCallNotificationResult
+
+/** Indicates whether the notification has timed out or not. */
+@property (nonatomic, readonly, assign) BOOL isTimedOut;
+
+/** Identifier of the user from which the call represented by the notification originated. */
+@property (nonatomic, readonly, copy) NSString *remoteUserId;
+
+/** A unique identifier pertaining to the call */
+@property (nonatomic, readonly, copy) NSString *callId;
+
+/**
+ * Hint that indicates if video is offered in the call.
+ */
+@property (nonatomic, readonly, getter=isVideoOffered) BOOL videoOffered;
+
+/**
+ * If isCallCanceled is true, then the notification indicates the remote party canceled the call.
+ */
+@property (nonatomic, readonly, getter=isCallCanceled) BOOL callCanceled;
+
+/**
+ * Return headers set by the caller when initiating the call.
+ *
+ * @see - [SINCallClient callUserWithId: headers:].
+ */
+@property (nonatomic, readonly, copy) NSDictionary *headers;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINClient.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINClient.h
new file mode 100644
index 00000000..9d0e5e8d
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINClient.h
@@ -0,0 +1,512 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+#import
+#import
+#import
+#import
+
+SIN_EXPORT SIN_EXTERN NSString *const SINClientDidStartNotification;
+SIN_EXPORT SIN_EXTERN NSString *const SINClientDidFailNotification;
+SIN_EXPORT SIN_EXTERN NSString *const SINClientWillTerminateNotification;
+
+#pragma mark - Log Severity
+
+#pragma mark - SINClient
+
+/**
+ * The SINClient is the Sinch SDK entry point.
+ *
+ * It provides access to the feature classes in the Sinch SDK:
+ * SINMessageClient, SINCallClient and SINAudioController.
+ * It is also used to configure the user's and device's capabilities.
+ *
+ * ### User Identification
+ *
+ * The user IDs that are used to identify users application specific.
+ * If the app already has a scheme for user IDs (email addresses,
+ * phone numbers, customer numbers, etc.), the same ID could be used
+ * when configuring the SINClient.
+ *
+ * ### Example
+ *
+ * // Instantiate a client object using the client factory.
+ * id sinchClient = [Sinch clientWithApplicationKey:@""
+ * applicationSecret:@""
+ * environmentHost:@"clientapi.sinch.com"
+ * userId:@""];
+ *
+ * // Specify the client capabilities. At least one of the messaging or calling capabilities should be enabled.
+ * [sinchClient setSupportCalling:YES];
+ * [sinchClient setSupportMessaging:YES];
+ * [sinchClient setSupportPushNotifications:YES]; // (optional)
+ *
+ * // Set your delegate object
+ * sinchClient.delegate = ... ;
+ *
+ * // Start the client
+ * [sinchClient start];
+ *
+ * // Start listening for incoming events (calls and messages).
+ * [sinchClient startListeningOnActiveConnection];
+ *
+ * // Use the SINCallClient to place and receive calls
+ * // Use the SINMessageClient to send and receive messages
+ *
+ * // Stop listening for incoming events (calls and messages).
+ * [sinchClient stopListeningOnActiveConnection];
+ *
+ * // Terminate the client when the calling and messaging functionalities are no longer needed.
+ * [sinchClient terminate];
+ */
+@protocol SINClient
+
+/**
+ * The object that acts as the delegate of the receiving client.
+ *
+ * The delegate object handles call state change events and must
+ * adopt the SINClientDelegate protocol.
+ *
+ * @see SINClientDelegate
+ */
+@property (nonatomic, weak) id delegate;
+
+/**
+ * ID of the local user
+ */
+@property (nonatomic, readonly, copy) NSString *userId;
+
+/**
+ *
+ * Specify whether this device should support making and receiving calls.
+ * Default is NO.
+ *
+ * Method should be called before calling -[SINClient start].
+ *
+ * @param supported Enable or disable support making and receiving calls.
+ * @see SINCallClient
+ *
+ */
+- (void)setSupportCalling:(BOOL)supported;
+
+/**
+ *
+ * Specify the data protection type (NSFileProtectionType) for the files created and used by the Sinch SDK.
+ * If not set specifically, the files will inherit the data protection level defined in your Application.
+ *
+ * Method should be called before calling -[SINClient start].
+ *
+ * @param type the data protection type applied to the files created by the Sinch SDK.
+ *
+ */
+- (void)setDataProtectionType:(NSFileProtectionType)type;
+
+/**
+ * Specify whether this application should support sending and receiving instant messages.
+ * Default is NO.
+ *
+ * Method should be called before calling -[SINClient start].
+ *
+ * @param supported Enable or disable support for instant messaging.
+ *
+ * @see SINMessageClient
+ *
+ */
+- (void)setSupportMessaging:(BOOL)supported;
+
+/**
+ * Specify whether this device should receive incoming calls via push
+ * notifications.
+ *
+ * Method should be called before calling -[SINClient start].
+ *
+ * @param supported Enable or disable support for push notifications.
+ *
+ * @see -[SINClient registerPushNotificationData:]
+ * @see -[SINClient unregisterPushNotificationData];
+ * @see -[SINClient relayRemotePushNotificationPayload:];
+ *
+ */
+- (void)setSupportPushNotifications:(BOOL)supported;
+
+/**
+ * Specify that the Sinch SDK and platform should take care of
+ * sending the push notification to the other device via the appropriate
+ * push notification gateway (i.e. Apple Push Notification Service for iOS devices,
+ * and Google Cloud Messaging (GCM) for Android devices).
+ *
+ * (This require that you have uploaded your Apple Push Notification
+ * Certificate(s) on the Sinch website)
+ *
+ * This method will internally also invoke -[SINClient setSupportPushNotifications:YES]
+ *
+ * Method should be called before calling -[SINClient start].
+ *
+ * @see -[SINClient registerPushNotificationDeviceToken:type:apsEnvironment:]
+ * @see -[SINClient unregisterPushNotificationDeviceToken];
+ * @see -[SINClient relayRemotePushNotificationPayload:];
+ *
+ */
+- (void)enableManagedPushNotifications;
+
+/**
+ * [DEPRECATED] Specify whether to keep the active connection open if the application
+ * leaves foreground.
+ *
+ * If specified to be supported, the active connection which is used for
+ * receiving incoming calls will be kept open even if the application leaves
+ * foreground. Enabling this also requires that 'voip' is specified for
+ * UIBackgroundModes in the application's Info.plist.
+ *
+ * If specified to not be supported, the application will not be running in the
+ * background, and the active connection which is used for receiving incoming
+ * calls will be closed once the application leaves foreground.
+ * (Though it will be re-opened once the application returns to foreground).
+ * If not supported, the application will be required to rely on push
+ * notifications to receive incoming calls if the application leaves foreground.
+ *
+ * If specified to be supported, the client's delegate is required to implement
+ * additional parts of the SINClientDelegate protocol. It is required to
+ * implement -[SINClientDelegate client:localNotificationForIncomingCall:]
+ *
+ * This method should be called before calling -[SINClient start].
+ *
+ * @param supported Specifies whether the active connection should be kept open
+ * even if the application leaves foreground.
+ *
+ * @exception NSInternalInconsistencyException Throws exception if called after
+ * client is started.
+ *
+ */
+- (void)setSupportActiveConnectionInBackground:(BOOL)supported NS_DEPRECATED_IOS(4_0, 9_0, "Please use PushKit and SINManagedPush");
+
+/**
+ * Start client to enable the calling functionality.
+ *
+ * The client delegate should be set before calling the start method to
+ * guarantee that delegate callbacks are received as expected.
+ *
+ */
+- (void)start;
+
+/**
+ * Terminate client when the calling functionality is no longer needed.
+ *
+ * It is generally recommended to initiate the Sinch client, start it, but not
+ * terminate it, during the lifetime of the running application. If incoming calls
+ * are not desired for a limited period of time or similar scenarios, it is
+ * instead recommended to only stop listening for incoming calls via the method
+ * (-[SINClient stopListeningOnActiveConnection]).
+ * This is simply because initializing and starting the client is relatively
+ * resource intensive both in terms of CPU, as well as there is potentially
+ * network requests involved in stopping and re-starting the client.
+ *
+ * If desired to dispose the client, it is required to explicitly invoke terminate
+ * (or terminateGracefully) to relinquish certain resources.
+ * This method should always be called before the application code releases its
+ * last reference to the client.
+ *
+ */
+- (void)terminate;
+
+/**
+* Terminates the client, while still leaving it some time to finish up currently
+* pending tasks, for example finishing pending HTTP requests.
+*
+* See -[SINClient terminate].
+*/
+- (void)terminateGracefully;
+
+/**
+ * THIS METHOD IS DEPRECATED. See -[SINClient terminate]
+ */
+- (void)stop;
+
+/**
+ * Check whether client is successfully started.
+ *
+ * @return A boolean value indicating whether the client has successfully
+ * started and is ready to perform calling functionality.
+ */
+- (BOOL)isStarted;
+
+/**
+ * This will establish an active keep-alive connection as a signaling channel
+ * for receiving incoming calls.
+ *
+ * Note that the active connection will only be kept open while the application
+ * is running in foreground. To support receiving calls while the application is
+ * in the background (or not running), please use push notifications.
+ */
+- (void)startListeningOnActiveConnection;
+
+/**
+ * This will close the connection that is kept alive and used as signaling
+ * channel for receiving incoming calls. This method should be used when the
+ * application no longer intends to utilize the long-lived connection for
+ * receiving incoming calls.
+ *
+ * If the intention is to completely turn off incoming calls and the application
+ * is also using push notifications as a method of receiving
+ * incoming calls, then the application should also unregister previously
+ * registered push notification data via the method
+ * -[SINClient unregisterPushNotificationData].
+ *
+ */
+- (void)stopListeningOnActiveConnection;
+
+/**
+ * Method used to forward the Sinch-specific payload extracted from an incoming
+ * Apple Push Notification.
+ *
+ * @return Value indicating initial inspection of push notification payload.
+ *
+ * @param payload Sinch-specific payload which was transferred with an
+ * Apple Push Notification.
+ *
+ * @see SINNotificationResult
+ */
+- (id)relayRemotePushNotificationPayload:(NSString *)payload;
+
+/**
+ * Method used to forward a remote notification dictionary if using -[SINClient enableManagedPushNotifications];
+ *
+ * @return Value indicating initial inspection of push notification.
+ *
+ * @param userInfo Remote notification payload which was transferred with an Apple Push Notification.
+ * and received via -[UIApplicationDelegate application:didReceiveRemoteNotification:].
+ *
+ * @see SINNotificationResult
+ */
+- (id)relayRemotePushNotification:(NSDictionary *)userInfo;
+
+/**
+ * Method used to handle a local notification which has been scheduled and
+ * taken action upon by the application user.
+ *
+ * @return Value indicating outcome of the attempt to handle the notification.
+ *
+ * @param notification UILocalNotification
+ *
+ * @exception NSInternalInconsistencyException Throws exception if called before
+ * client startup has completed.
+ * A case when the client might not be started yet is if the
+ * application user takes action on an local notification that is not
+ * relevant any more. E.g. the user ignored the notification when it
+ * was first presented, then quit the app, and the notification was
+ * left in Notification Center and was taken action upon at a later
+ * time.
+ * Applications should relay all local notifications where sin_isSinchNotification
+ * is True.
+ *
+ *
+ * -[SINClient isStarted] may be used to guard against calling this
+ * method at inappropriate times.
+ *
+ * @see -[SINClient isStarted]
+ * @see SINNotificationResult
+ *
+ */
+- (id)relayLocalNotification:(UILocalNotification *)notification;
+
+/**
+ * Register device-specific data that can be used to identify this device
+ * and tie it to an Apple Push Notification device token.
+ *
+ * @param pushNotificationData Device-specific data that can be used to
+ * tie a device to a specific Apple Push
+ * Notification device token
+ *
+ * The `pushNotificationData` is what will be passed back in
+ * -[SINCallDelegate call:shouldSendPushNotifications:]
+ * in the caller's application, unless the application on the destination device
+ * (the device on which this method is called) is not running in the background,
+ * and is required to woken up it via a Apple Push Notification.
+ *
+ * See [UIApplication registerForRemoteNotificationTypes:] on how to obtain
+ * the current device token.
+ *
+ * @see SINCallDelegate
+ */
+- (void)registerPushNotificationData:(NSData *)pushNotificationData;
+
+/**
+ * Unregister previously registered device-specific data that is used to
+ * identify this device and tie it to an Apple Push Notification device token.
+ *
+ * If it is unwanted that the user receives further remote push notifications
+ * for Sinch calls, this method should be used to unregister the push data.
+ */
+- (void)unregisterPushNotificationData;
+
+/**
+ * Register push notification device token for using "Sinch Managed Push Notifications".
+ * The preferred way of enabling push notifications is to use `SINManagedPush` which
+ * will automatically register the device token with the client, but this method can
+ * also be used directly.
+ *
+ * @param deviceToken A token that identifies the device to APNs.
+ * @param pushType SINPushType NSString constant, i.e. SINPushTypeVoIP or SINPushTypeRemote
+ * @param apsEnvironment Specification of which Apple Push Notification Service environment
+ * the device token is bound to.
+ *
+ * @see SINAPSEnvironment
+ * @see SINPushTypeVoIP
+ * @see SINPushTypeRemote
+ */
+- (void)registerPushNotificationDeviceToken:(NSData *)deviceToken
+ type:(NSString *)pushType
+ apsEnvironment:(SINAPSEnvironment)apsEnvironment;
+
+/**
+ * Unregister push notification device token when using "Sinch Managed Push Notifications"
+ * Example if the user log out, the device token should be unregistered.
+ */
+- (void)unregisterPushNotificationDeviceToken;
+
+/**
+ * Specify a display name to be used when the Sinch client sends a push notification on
+ * behalf of the local user (e.g. for an outgoing call).
+ * This will only be used when using -[SINClient enableManagedPushNotifications].
+ *
+ * Display name is included in a push notification on a best-effort basis. For example, if the
+ * target device has very limited push payload size constraints (e.g iOS 7 can only handle
+ * 255 byte push notification payload), then the display name may not be included.
+ *
+ * @param displayName display name may at most be 255 bytes (UTF-8 encoded) long.
+ */
+- (void)setPushNotificationDisplayName:(NSString *)displayName;
+
+/**
+ *
+ * Returns the call client object for placing and receiving calls.
+ *
+ * @see - [SINClient setSupportCalling:]
+ *
+ */
+- (id)callClient;
+
+/**
+ *
+ * Returns the message client object for sending messages and adding
+ * delegates for message events.
+ *
+ * @see - [SINClient setSupportMessaging:]
+ *
+ */
+- (id)messageClient;
+
+/**
+ * Retrieve the interface for the audio controller, which provides access
+ * to various audio related functionality, such as muting the microphone,
+ * enabling the speaker, and playing ring tones.
+ */
+- (id)audioController;
+
+/**
+ * Retrieve the interface for the video controller, which provides
+ * access to video related functionality.
+ */
+- (id)videoController;
+
+@end
+
+/**
+ * The delegate of a SINClient object must adopt the SINClientDelegate
+ * protocol. The required methods handle client state changes and the
+ * optional log method allows the delegate to log messages from the
+ * underlying calling functionality.
+ *
+ * When an incoming call has been received,
+ * [SINClientDelegate client:didReceiveIncomingCall:] is called.
+ * The delegate of the incoming call object should be set at this time.
+ */
+@protocol SINClientDelegate
+
+/**
+ * Tells the delegate that the client started the calling functionality.
+ *
+ * @param client The client informing the delegate that the calling
+ * functionality started successfully.
+ *
+ * @see SINClient
+ */
+- (void)clientDidStart:(id)client;
+
+/**
+ * Tells the delegate that a client failure occurred.
+ *
+ * @param client The client informing the delegate that it
+ * failed to start or start listening.
+ *
+ * @param error Error object that describes the problem.
+ *
+ * @see SINClient
+ */
+- (void)clientDidFail:(id)client error:(NSError *)error;
+
+@optional
+
+/**
+ * DEPRECATED. Do not use.
+ */
+- (void)clientDidStop:(id)client;
+
+/**
+ * Tells the delegate that it is required to provide additional registration
+ * credentials.
+ *
+ * @param client The client informing the delegate that it requires
+ * additional registration details.
+ *
+ * @param registrationCallback The callback object that is to be called
+ * when registration credentials have been fetched.
+ *
+ * @see SINClientRegistration
+ * @see SINClient
+ */
+- (void)client:(id)client requiresRegistrationCredentials:(id)registrationCallback;
+
+/**
+ * The delegate object can choose to subscribe to log messages from
+ * the underlying calling functionality by implementing this method.
+ *
+ * The easiest way to log the messages is to simply write them to
+ * the device console using NSLog:
+ *
+ * `NSLog(@"[%@] %u %@", timestamp, severity, message);`
+ *
+ * *Caution:* Only log messages with severity level `SINLogSeverityWarn`
+ * or higher to the console in release builds, to avoid flooding the
+ * device console with debugging messages.
+ *
+ * @param client The client that the log messages are coming from.
+ *
+ * @param message The message that is being logged.
+ *
+ * @param area The area that the log message relates to.
+ *
+ * @param severity The severity level of the log message. It may be one of
+ * the following:
+ *
+ * - `SINLogSeverityTrace`
+ * - `SINLogSeverityInfo`
+ * - `SINLogSeverityWarn`
+ * - `SINLogSeverityCritical`
+ *
+ * @param timestamp The time when the message was logged.
+ *
+ * @see SINClient
+ */
+- (void)client:(id)client
+ logMessage:(NSString *)message
+ area:(NSString *)area
+ severity:(SINLogSeverity)severity
+ timestamp:(NSDate *)timestamp;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINClientRegistration.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINClientRegistration.h
new file mode 100644
index 00000000..122da0a2
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINClientRegistration.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+/**
+ * Callback object to be used to proceed in user registration process when
+ * registration credentials for the user in question have been obtained.
+ */
+@protocol SINClientRegistration
+
+/**
+ * Proceed with user registration by providing a valid signature and sequence
+ * which will be used in signing the registration request.
+ *
+ * @param signature Signature which have been obtained for a specific
+ * user and sequence.
+ *
+ * @param sequence Sequence identifier for the correspoding signature
+ *
+ *
+ * @see SINClient, SINClientDelegate
+ *
+ */
+- (void)registerWithSignature:(NSString *)signature sequence:(uint64_t)sequence;
+
+/**
+ * If the application fails to provide a signature and sequence, it must
+ * notify the Sinch client via this method.
+ *
+ * Calling this method will have the effect that the client delegate will
+ * receive a call to -[SINClientDelegate clientDidFail:error:].
+ *
+ * @param error Error that prevented obtaining a registration sequence and
+ * signature.
+ *
+ * @see SINClient, SINClientDelegate
+ *
+ */
+- (void)registerDidFail:(NSError *)error;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINError.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINError.h
new file mode 100644
index 00000000..67065851
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINError.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+#ifndef SIN_ERROR_H
+#define SIN_ERROR_H
+
+SIN_EXPORT SIN_EXTERN NSString *const SINErrorDomainNetwork;
+SIN_EXPORT SIN_EXTERN NSString *const SINErrorDomainCapability;
+SIN_EXPORT SIN_EXTERN NSString *const SINErrorDomainOther;
+
+#endif // SIN_ERROR_H
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINExport.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINExport.h
new file mode 100644
index 00000000..c59a6492
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINExport.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#ifndef SIN_EXPORT
+#define SIN_EXPORT __attribute__((visibility("default")))
+#endif
+
+#ifndef SIN_EXTERN
+#ifdef __cplusplus
+#define SIN_EXTERN extern "C"
+#else
+#define SIN_EXTERN extern
+#endif
+#endif
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINForwardDeclarations.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINForwardDeclarations.h
new file mode 100644
index 00000000..5a3a4ab4
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINForwardDeclarations.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+@protocol SINClient;
+@protocol SINClientRegistration;
+@protocol SINClientDelegate;
+
+@protocol SINCallClient;
+@protocol SINCall;
+
+@protocol SINMessageClient;
+
+@protocol SINManagedPush;
+@protocol SINNotificationResult;
+@class SINLocalNotification;
+@class UILocalNotification;
+@class SINPushHelper;
+
+@protocol SINAudioController;
+@protocol SINVideoController;
+@protocol SINVideoFrameCallback;
+@protocol SINVideoFrame;
+@protocol SINLocalVideoFrameCallback;
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINLocalNotification.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINLocalNotification.h
new file mode 100644
index 00000000..2d69437d
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINLocalNotification.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+#import
+
+/**
+ * SINLocalNotification can be used to specify presentation data for a local
+ * push that is to be used for an incoming call.
+ *
+ * The properties are mirroring the properties available for UILocalNotification.
+ *
+ * @see UILocalNotification
+ */
+SIN_EXPORT
+@interface SINLocalNotification : NSObject
+
+/**
+ * The message displayed in the notification alert.
+ *
+ * Assign a string or, preferably, a localized-string key
+ * (using NSLocalizedString) as the value of the message. If the value of this
+ * property is non-nil, an alert is displayed. The default value is nil
+ * (no alert).
+ */
+@property (nonatomic, copy) NSString *alertBody;
+
+/**
+ * A Boolean value that controls whether the notification shows or hides the
+ * alert action.
+ *
+ * Assign NO to this property to hide the alert button or slider.
+ * (This effect requires alertBody to be non-nil.) The default value is YES.
+ */
+@property (nonatomic) BOOL hasAction;
+
+/**
+ * The title of the action button or slider.
+ *
+ * Assign a string or, preferably, a localized-string key
+ * (using NSLocalizedString) as the value. The alert action is the title of the
+ * right button of the alert or the value of the unlock slider, where the value
+ * replaces “unlock” in “slide to unlock”. If you specify nil, and alertBody is
+ * non-nil, “View” (localized to the preferred language) is used as the default
+ * value.
+ */
+@property (nonatomic, copy) NSString *alertAction;
+
+/**
+ * Identifies the image used as the launch image when the user taps (or slides)
+ * the action button (or slider).
+ *
+ * The string is a filename of an image file in the application bundle.
+ * This image is a launching image specified for a given notification;
+ * when the user taps the action button (for example, “View”) or moves the
+ * action slider, the image is used in place of the default launching image.
+ * If the value of this property is nil (the default), the system either uses
+ * the previous snapshot, uses the image identified by the UILaunchImageFile key
+ * in the application’s Info.plist file, or falls back to Default.png.
+ */
+@property (nonatomic, copy) NSString *alertLaunchImage;
+
+/**
+ * The name of the file containing the sound to play when an alert is displayed.
+ *
+ * For this property, specify the filename (including extension) of a sound
+ * resource in the application’s main bundle or
+ * UILocalNotificationDefaultSoundName to request the default system sound.
+ * When the system displays an alert for a local notification or badges an
+ * application icon, it plays this sound.
+ * The default value is nil (no sound).
+ * Sounds that last longer than 30 seconds are not supported. If you specify a
+ * file with a sound that plays over 30 seconds, the default sound is played
+ * instead.
+ *
+ */
+@property (nonatomic, copy) NSString *soundName;
+
+/**
+ * The number to display as the application’s icon badge.
+ *
+ * The default value is 0, which means "no change.” The application should use
+ * this property’s value to increment the current icon badge number, if any.
+ */
+@property (nonatomic) NSInteger applicationIconBadgeNumber;
+
+/**
+ * Category of the local notification, as passed to
+ * +[UIUserNotificationSettings settingsForUserNotificationTypes:userNotificationActionSettings:]
+ * The value of this property is nil by default.
+ * @see UILocalNotification.category
+ */
+@property (nonatomic, copy) NSString *category NS_AVAILABLE_IOS(8_0);
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINLocalVideoFrameCallback.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINLocalVideoFrameCallback.h
new file mode 100644
index 00000000..09dcacb3
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINLocalVideoFrameCallback.h
@@ -0,0 +1,29 @@
+#import
+#import
+
+@protocol SINLocalVideoFrameCallback
+
+/**
+ * This method is called when a new frame is captured from the camera.
+ * The produced video frames are in CVPixelBufferRef format. It provides
+ * the possibility for developer to process the local video frames (e.g.
+ * applying filters on the frames), and send the updated video frames to
+ * the remote client.
+ *
+ * IMPORTANT: the developer needs to retain the CVPixelBuffer object
+ * received from the callback by CVPixelBufferRetain,and to release the
+ * object by CVPixelBufferRelease.
+ *
+ * @param cvPixelBuffer The video frame captured from the camera.
+ * @param completionHandler The completionHandler needs to be invoked with
+ * a cvPixelBuffer object which will be sent to the remote peer.
+ *
+ * IMPORTANT: The invocation of the completionHandler is mandatory when
+ * SINLocalVideoFrameCallback is set, otherwise the Sinch SDK will not send
+ * any frame to the remote peer in this case.
+ */
+
+- (void)onFrame:(CVPixelBufferRef)cvPixelBuffer
+ completionHandler:(void (^)(CVPixelBufferRef retCVPixelBuffer))completionHandler;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINLogSeverity.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINLogSeverity.h
new file mode 100644
index 00000000..95a9f4cb
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINLogSeverity.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#ifndef SIN_LOG_SEVERITY_H
+#define SIN_LOG_SEVERITY_H
+
+#ifndef SIN_LOG_SEVERITY_
+#define SIN_LOG_SEVERITY_
+typedef NS_ENUM(NSInteger, SINLogSeverity) {
+ SINLogSeverityTrace = 0,
+ SINLogSeverityInfo,
+ SINLogSeverityWarn,
+ SINLogSeverityCritical
+};
+#endif // SIN_LOG_SEVERITY_
+
+#endif // SIN_LOG_SEVERITY_H
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINManagedPush.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINManagedPush.h
new file mode 100644
index 00000000..1bb7b58a
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINManagedPush.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+#import
+#import
+#import
+
+SIN_EXPORT SIN_EXTERN NSString *const SINPushTypeVoIP NS_AVAILABLE_IOS(8_0);
+SIN_EXPORT SIN_EXTERN NSString *const SINPushTypeRemote NS_AVAILABLE_IOS(6_0);
+
+// SINApplicationDidReceiveRemoteNotification is emitted for both VoIP and Remote Push Notifications.
+// Also emitted for remote notifications received at application launched (i.e. via
+// UIApplicationDidFinishLaunchingNotification with UIApplicationLaunchOptionsRemoteNotificationKey)
+// SINApplicationDidReceiveRemoteNotification provides a unified way of listening for incoming remote notifications.
+SIN_EXPORT SIN_EXTERN NSString *const SINApplicationDidReceiveRemoteNotification;
+
+// SINRemoteNotificationKey
+// userInfo contains NSDictionary with payload
+SIN_EXPORT SIN_EXTERN NSString *const SINRemoteNotificationKey;
+
+// SINPushTypeKey
+// userInfo contains this key with value SINPushTypeVoIP or SINPushTypeRemote
+SIN_EXPORT SIN_EXTERN NSString *const SINPushTypeKey;
+
+/**
+ * SINManagedPush is a helper class to manage push notification credentials both
+ * for regular Remote Push Notifications and VoIP Push Notifications (which is
+ * available since iOS 8).
+ *
+ * SINManagedPush acts as a facade for registering for device tokens for both
+ * types of notifications, and can also automatically register any received push
+ * credentials to any active SINClient.
+ *
+ * SINManagedPush simplifies scenarios such as when receiving a device token
+ * occur before creating a SINClient. In such a case, SINManagedPush can
+ * automatically register the device token when the SINClient is created and
+ * started.
+ *
+ * ### Example
+ *
+ * -(BOOL)application:(UIApplication *)application
+ * didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ * self.push = [Sinch managedPushWithAPSEnvironment:SINAPSEnvironmentAutomatic]
+ * [self.push setDesiredPushTypeAutomatically];
+ * [self.push registerUserNotificationSettings];
+ * }
+ *
+ */
+
+@protocol SINManagedPushDelegate;
+
+@protocol SINManagedPush
+
+@property (nonatomic, readwrite, weak) id delegate;
+
+/**
+ * Specify what user notification types should be used for remote push notifications.
+ *
+ * @property userNotificationTypes
+ *
+ * Defaults to UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge
+ *
+ * userNotificationTypes should be set before invoking -[SINManagedPush setDesiredPushType:] or
+ * -[SINManagedPush setDesiredPushTypeAutomatically].
+ */
+@property (nonatomic, readwrite, assign) UIUserNotificationType userNotificationTypes;
+
+/**
+ * Requests registration of either VoIP remote notifications or regular remote
+ * notifications (similar to PushKit's -[PKPushRegistry setDesiredPushTypes:]).
+ *
+ * @param pushType Desired SINPushType NSString constant, e.g. SINPushTypeVoIP or SINPushTypeRemote
+ */
+- (void)setDesiredPushType:(NSString *)pushType;
+
+/**
+ * Set desired push type based on runtime detection of iOS version and whether
+ * PushKit is linked or not. This method will invoke `-[self setDesiredPushType:SINPushTypeVoIP]`
+ * if PushKit is linked, else `-[self setDesiredPushType:SINPushTypeRemote]`.
+ */
+- (void)setDesiredPushTypeAutomatically;
+
+/**
+ * Similar to -[UIApplication registerUserNotificationSettings:], this will
+ * register user notification settings based on `-[SINManagedPush
+ * userNotificationTypes]`.
+ *
+ * On iOS 8 or higher it will invoke `-[UIApplication registerUserNotificationSettings:]` and
+ * on iOS 7 or lower it will invoke `-[UIApplication registerForRemoteNotificationTypes:]`.
+ */
+- (void)registerUserNotificationSettings;
+
+/**
+ * Specify a display name to be used when Sinch sends a push notification on
+ * behalf of the local user (e.g. for an outgoing call). This method will
+ * automatically invoke `-[SINClient setPushNotificationDisplayName:]` when a
+ * new Sinch client is started.
+ *
+ * @param displayName Display name that will be injected into remote push notification
+ * alert message.
+ *
+ * Display name will be injected into the localization string SIN_INCOMING_CALL_DISPLAY_NAME.
+ * It will also be passed along in Google Cloud Messaging push notifications if a remote
+ * user's device is an Android device.
+ *
+ * @see SINClient
+ */
+- (void)setDisplayName:(NSString *)displayName;
+
+#pragma mark - Methods to be delegated from UIApplicationDelegate
+
+- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
+
+- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
+
+@end
+
+@protocol SINManagedPushDelegate
+
+/**
+ * Tells the delegate that a remote notification was received. The remote notification may be either a VoIP remote
+ * push notification, or a regular push remote notification.
+ *
+ * @param managedPush managed push instance that received the push notification
+ * @param payload The dictionary payload that the remote push notification carried.
+ * @param pushType SINPushTypeVoIP or SINPushTypeRemote
+ */
+- (void)managedPush:(id)managedPush
+ didReceiveIncomingPushWithPayload:(NSDictionary *)payload
+ forType:(NSString *)pushType;
+@end
+
+@interface NSDictionary (SINRemoteNotificationAdditions)
+
+/**
+ * Category method to determine whether a remote push notification dictionary payload
+ * is carrying a Sinch payload.
+ */
+- (BOOL)sin_isSinchPushPayload;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessage.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessage.h
new file mode 100644
index 00000000..0cdd0816
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessage.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+/**
+ * SINMessage represents an instant message.
+ *
+ * (Also see SINOutgoingMessage.h)
+ *
+ **/
+@protocol SINMessage
+
+/** String that is used as an identifier for this particular message. */
+@property (nonatomic, readonly) NSString* messageId;
+
+/** Array of ids of the recipients of the message. */
+@property (nonatomic, readonly) NSArray* recipientIds;
+
+/** The id of the sender of the message. */
+@property (nonatomic, readonly) NSString* senderId;
+
+/** Message body text */
+@property (nonatomic, readonly) NSString* text;
+
+/**
+ * Message headers
+ *
+ * Any application-defined message meta-data
+ * can be passed via headers.
+ *
+ * E.g. a human-readable "display name / username"
+ * can be convenient to send as an application-defined
+ * header.
+ *
+ **/
+@property (nonatomic, readonly) NSDictionary* headers;
+
+/**
+ * Message timestamp
+ *
+ * Server-side-based timestamp for the message.
+ * May be nil for message which is created locally, i.e. an outgoing message.
+ */
+@property (nonatomic, readonly) NSDate* timestamp;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageClient.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageClient.h
new file mode 100644
index 00000000..5dac77cd
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageClient.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+@protocol SINMessageClientDelegate;
+@protocol SINMessage;
+@class SINOutgoingMessage;
+@protocol SINMessageDeliveryInfo;
+@protocol SINMessageFailureInfo;
+
+/**
+ *
+ * SINMessageClient provides the entry point to the messaging functionality of the Sinch SDK.
+ * A SINMessageClient can be acquired via SINClient.
+ *
+ * ### Example
+ *
+ * [sinchClient setSupportMessaging:YES];
+ * [sinchClient start];
+ * ...
+ *
+ * // Get the message client from the sinchClient
+ * SINMessageClient messageClient = [sinchClient messageClient];
+ *
+ * // Assign a delegate for instant messages events
+ * messageClient.delegate = ...
+ *
+ * //Send a message
+ * SINOutgoingMessage *message = [SINOutgoingMessage messageWithRecipient:@" text:@"Hi there!"];
+ * [messageClient sendMessage:message];
+ *
+ */
+@protocol SINMessageClient
+
+/**
+ * Assigns a delegate to the Message Client.
+ *
+ * Applications implementing instant messaging should assign a delegate
+ * adopting the SINMessageClientDelegate protocol. The delegate will be
+ * notified when messages arrive and receive message status updates.
+ *
+ * @see SINMessageClientDelegate
+ */
+
+@property (nonatomic, weak) id delegate;
+
+/**
+ * Sends an outgoing message.
+ *
+ * Message progress is communicated via the SINMessageClientDelegate.
+ *
+ * *Note*: Do not send the same SINOutgoingMessage more than once.
+ *
+ * @see SINMessageClientDelegate
+ * @see +[SINOutgoingMessage messageWithRecipient:text:]
+ *
+ * @param message The message to be sent.
+ *
+ * @exception NSInvalidArgumentException Throws exception if message is invalid,
+ * e.g. if no recipient is set.
+ *
+ */
+- (void)sendMessage:(SINOutgoingMessage *)message;
+
+@end
+
+/**
+ *
+ * The message client delegate by which message events are communicated.
+ *
+ **/
+@protocol SINMessageClientDelegate
+
+/**
+ * Tells the delegate that a message has been received.
+ *
+ * @param messageClient The message client that is informing the delegate.
+ *
+ * @param message The incoming message.
+ *
+ * @see SINMessageClient, SINMessage
+ **/
+- (void)messageClient:(id)messageClient didReceiveIncomingMessage:(id)message;
+
+/**
+ * Tells the delegate that a message for a specific recipient has been sent by the local user.
+ *
+ * This method is called when a message is sent from
+ * the local message client (i.e. -[SINMessageClient sendMessage:]).
+ * This callback is triggered on all devices on which the local user is logged in.
+ *
+ * @param message Message that was sent.
+ *
+ * @param recipientId Recipient of the message
+ *
+ * @see SINMessageClient, SINMessage
+ */
+- (void)messageSent:(id)message recipientId:(NSString *)recipientId;
+
+/**
+ * Tells the delegate that a message has been delivered (to a particular
+ * recipient).
+ *
+ * @param info Info identifying the message that was delivered, and to whom.
+ *
+ **/
+- (void)messageDelivered:(id)info;
+
+/**
+ * Tells the delegate that the message client failed to send a message.
+ *
+ * *Note*: Do not attempt to re-send the SINMessage received, instead,
+ * create a new SINOutgoingMessage and send that.
+ *
+ * @param messageFailureInfo SINMessageFailureInfo object,
+ * identifying the message and for which recipient
+ * sending the message failed.
+ *
+ * @param message The message that could not be delivered.
+ **/
+- (void)messageFailed:(id)message info:(id)messageFailureInfo;
+
+@optional
+
+/**
+ * Tells the delegate that the receiver's device can't be reached directly,
+ * and it is required to wake up the receiver's application with a push
+ * notification.
+ *
+ * @param message The message for which pushing is required.
+ *
+ * @param pushPairs Array of SINPushPair. Each pair identififies a certain
+ * device that should be requested to be woken up via
+ * Apple Push Notification.
+ *
+ * The push data entries are equal to what the receiver's
+ * application passed to the method
+ * -[SINClient registerPushNotificationData:] method.
+ *
+ * @see SINPushPair
+ *
+ **/
+- (void)message:(id)message shouldSendPushNotifications:(NSArray *)pushPairs;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageDeliveryInfo.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageDeliveryInfo.h
new file mode 100644
index 00000000..b49174b3
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageDeliveryInfo.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+/**
+ * SINMessageDeliveryInfo contains additional information pertaining
+ * to a delivered message.
+ *
+ * @see -[SINMessageClientDelegate messageDelivered:].
+ */
+
+@protocol SINMessageDeliveryInfo
+
+/** The message's identifier */
+@property (nonatomic, readonly, copy) NSString *messageId;
+
+/** The identifier of the recipient */
+@property (nonatomic, readonly, copy) NSString *recipientId;
+
+/** Server-side-based timestamp */
+@property (nonatomic, readonly, copy) NSDate *timestamp;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageFailureInfo.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageFailureInfo.h
new file mode 100644
index 00000000..a3696dae
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageFailureInfo.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+/**
+ * SINMessageFailureInfo contains additional information pertaining to
+ * failing to send a message.
+ * @see -[SINMessageClientDelegate messageFailed:info:].
+ */
+
+@protocol SINMessageFailureInfo
+
+/** The message's identifier */
+@property (nonatomic, readonly, copy) NSString *messageId;
+
+/** The identifier of the recipient */
+@property (nonatomic, readonly, copy) NSString *recipientId;
+
+/** The error reason */
+@property (nonatomic, readonly, copy) NSError *error;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageNotificationResult.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageNotificationResult.h
new file mode 100644
index 00000000..c39a7ac8
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINMessageNotificationResult.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+/**
+ * SINMessageNotificationResult is used to indicate the outcome of invoking
+ * the method -[SINClient relayRemotePushNotificationPayload:] in the case that
+ * the notification payload represents an instant message.
+ *
+ * SINMessageNotificationResult contains a `messageId`, and `senderId`.
+ * The `messageId` can for example be very useful when the application, upon
+ * receiving the notification, needs to direct the user to to a view that
+ * displays/highlights this particular message.
+ *
+ */
+
+@protocol SINMessageNotificationResult
+
+/** The message's id */
+@property (nonatomic, readonly, copy) NSString *messageId;
+
+/** The sender's user id */
+@property (nonatomic, readonly, copy) NSString *senderId;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINNotificationResult.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINNotificationResult.h
new file mode 100644
index 00000000..4ec4a435
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINNotificationResult.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+/**
+ * SINNotificationResult is used to indicate the result of calling the methods
+ * -[SINClient relayLocalNotification:] and
+ * -[SINClient relayRemotePushNotificationPayload:] .
+ *
+ * When a user acts on a notification, either a Push Notification, or a Local
+ * Notification, the Sinch-specific payload which is embedded in the
+ * notification may represent either an incoming call, or an incoming instant-
+ * message. SINNotificationResult is used to give information about what the
+ * notification payload represented, and can thus be used by the application to
+ * take appropriate actions.
+ *
+ * See SINCallNotificationResult and SINMessageNotificationResult for additional
+ * details.
+ *
+ * Example use:
+ *
+ * id result = [self.client relayLocalNotification:notification];
+ *
+ * if ([result isCall] && [[result callResult] isTimedOut]) {
+ * NSString* remoteUserId = [[result callResult] remoteUserId];
+ * // present UIAlert indicating user has a missed call.
+ * } else if([result isMessage]){
+ * NSString* messageId = [[result messageResult] messageId];
+ * // show view controller that highlights the particular message
+ * }
+ *
+ *
+ * It can be especially useful for scenarios which will not result in
+ * the SINClientDelegate receiving any callback for an incomnig call as a result
+ * of calling the methods mentioned above. One such scenario is when a user
+ * have been attempted to be reached, but not acted on the notification directly.
+ * In that case, the notification result object can indicate that the
+ * notification is too old (`isTimedOut`), and also contains the `remoteUserId` which can be
+ * used for display purposes.
+ *
+ */
+
+@protocol SINCallNotificationResult;
+@protocol SINMessageNotificationResult;
+
+@protocol SINNotificationResult
+
+/** Indicates whether the notification is valid or not. */
+@property (nonatomic, readonly, assign) BOOL isValid;
+
+/** Indicates whether the notification is call related */
+- (BOOL)isCall;
+
+/** If the notification is call related (isCall is true), callResult contains the notification result */
+- (id)callResult;
+
+/** Indicates whether the notification is message related */
+- (BOOL)isMessage;
+
+/** If the notification is message related (isMessage is true), messageResult contains the notification result */
+- (id)messageResult;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINOutgoingMessage.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINOutgoingMessage.h
new file mode 100644
index 00000000..2227d1c8
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINOutgoingMessage.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+/**
+ * SINOutgoingMessage should be used to create outgoing instant-messages.
+ */
+SIN_EXPORT
+@interface SINOutgoingMessage : NSObject
+
+/** String that is used as an identifier for this particular message. */
+@property (nonatomic, readonly) NSString *messageId;
+
+/** Array of ids of the recipients of the message. */
+@property (nonatomic, readonly) NSArray *recipientIds;
+
+/** Message body text */
+@property (nonatomic, readonly) NSString *text;
+
+/** Message headers */
+@property (nonatomic, readonly) NSDictionary *headers;
+
+/**
+ * Creates a new message with the specified recipient and message body.
+ * @exception NSInvalidArgumentException Throws exception if message is invalid,
+ * e.g. if no recipient is set or text is nil.
+ *
+ * @param recipientId The indended recipient's id.
+ * @param text Message text
+ */
++ (SINOutgoingMessage *)messageWithRecipient:(NSString *)recipientId text:(NSString *)text;
+
+/**
+ * Creates a new message with the specified recipients and message body.
+ * @exception NSInvalidArgumentException Throws exception if message is invalid,
+ * e.g. if no recipient is set or text is nil.
+ *
+ * @param recipientIds The indended recipients' ids.
+ * @param text Message text
+ */
++ (SINOutgoingMessage *)messageWithRecipients:(NSArray *)recipientIds text:(NSString *)text;
+
+/**
+ * Creates a SINOutgoingMessage from a SINMessage.
+ *
+ * @param message The original message
+ *
+ * @return A new sendable message. This message will have the same contents
+ * as the previous message but with a new id.
+ *
+ */
++ (SINOutgoingMessage *)messageWithMessage:(id)message;
+
+/**
+ * Add a message header
+ *
+ * The total size of header keys + values (when encoded with
+ * NSUTF8StringEncoding) must not exceed 1024 bytes.
+ *
+ * @param value Header value
+ * @param key Header key
+ */
+- (void)addHeaderWithValue:(NSString *)value key:(NSString *)key;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINPushHelper.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINPushHelper.h
new file mode 100644
index 00000000..5d62736a
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINPushHelper.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2018 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+#import
+
+SIN_EXPORT
+@interface SINPushHelper : NSObject
+
+/**
+ * Method used to parse a remote notification dictionary if using -[SINClient enableManagedPushNotifications];
+ *
+ * @return Value indicating initial inspection of push notification.
+ *
+ * @param userInfo Remote notification payload which was transferred with an Apple Push Notification.
+ * and received via -[UIApplicationDelegate application:didReceiveRemoteNotification:].
+ *
+ * @see SINNotificationResult
+ */
++ (id)queryPushNotificationPayload:(NSDictionary *)userInfo;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINPushPair.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINPushPair.h
new file mode 100644
index 00000000..40e14e13
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINPushPair.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+/**
+ * The SINPushPair contains push related information.
+ */
+@protocol SINPushPair
+
+/**
+ * The push data to use when forwarding the push payload to a device. The push
+ * data is equal to the data passed to -[SINClient registerPushNotificationData:].
+ */
+@property (nonatomic, retain) NSData* pushData;
+
+/**
+ * The push payload contains call/instant-message information encoded by
+ * the Sinch SDK. The payload should considered opaque to the
+ * application developer, and should be delivered to the callee/ recipient of
+ * an instant-message via appropriate push service, e.g. Apple Push Notification
+ * Service (APNS) or Google Cloud Messaging (GCM). Once received on the
+ * destination device, it should be passed to the
+ * Sinch via the method -[SINClient relayRemotePushNotificationPayload:].
+ */
+@property (nonatomic, retain) NSString* pushPayload;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINUILocalNotification+Sinch.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINUILocalNotification+Sinch.h
new file mode 100644
index 00000000..f2fda543
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINUILocalNotification+Sinch.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+#import
+
+/**
+ * SINLocalNotificationSinchAdditions is a set of category methods for
+ * the UILocalNotification class.
+ *
+ */
+
+@interface UILocalNotification (SINLocalNotificationSinchAdditions)
+
+/**
+ * Indicates that the UILocalNotification was created by the Sinch SDK
+ */
+- (BOOL)sin_isSinchNotification;
+
+/**
+ * The UILocalNotification represents an incoming call
+ */
+- (BOOL)sin_isIncomingCall;
+
+/**
+ * The UILocalNotification represents a missed call
+ */
+- (BOOL)sin_isMissedCall;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINUIView+Fullscreen.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINUIView+Fullscreen.h
new file mode 100644
index 00000000..251f64ad
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINUIView+Fullscreen.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+/**
+ * SINUIViewFullscreenAdditions are helper methods (implemented as Objective-C
+ * category methods) to make views go to full screen mode (and back to it's
+ * previous state)
+ */
+
+@interface UIView (SINUIViewFullscreenAdditions)
+
+/**
+ * @return YES if view is in full screen mode or is about to be (in animation transition).
+ */
+- (BOOL)sin_isFullscreen;
+
+/**
+ * Make view go into full screen mode.
+ *
+ * The view will be moved out of it's current place in the view hierarchy and will
+ * be added as a subview directly in the main UIWindow.
+ */
+- (void)sin_enableFullscreen:(BOOL)animated;
+
+/**
+ * Make view go back to it's original state before full screen mode was enabled.
+ *
+ * The view will be moved back to it's original superview, and it's original frame will be restored.
+ */
+- (void)sin_disableFullscreen:(BOOL)animated;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINVideoController.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINVideoController.h
new file mode 100644
index 00000000..4ce6dea0
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINVideoController.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+#import
+#import
+#import
+#import
+
+@protocol SINVideoController
+
+/**
+ * Indicates the capture device position (front-facing or back-facing
+ * camera) currently in use. This property may be set to to change
+ * which capture device should be used.
+ */
+@property (nonatomic, assign, readwrite) AVCaptureDevicePosition captureDevicePosition;
+
+/**
+ * Automatically set/unset UIApplication.idleTimerDisabled when video capturing is started / stopped.
+ * Default is YES.
+ */
+@property (nonatomic, assign, readwrite) BOOL disableIdleTimerOnCapturing;
+
+/**
+ * View into which the remote peer video stream is rendered.
+ *
+ * Use -[UIView contentMode] to control how the video frame is rendered.
+ * (Note that only UIViewContentModeScaleAspectFit and UIViewContentModeScaleAspectFill will be respected)
+ *
+ * Use -[UIView backgroundColor] to specify color for potential "empty" regions
+ * when UIViewContentModeScaleAspectFit is used.
+ *
+ * @see SINUIViewFullscreenAdditions (SINUIView+Fullscreen.h) for helpers to toggle full screen.
+ */
+- (UIView*)remoteView;
+
+/**
+ * View into which the locally captured video stream is rendered.
+ *
+ * Use -[UIView contentMode] to control how the video frame is rendered.
+ * (Note that only UIViewContentModeScaleAspectFit and UIViewContentModeScaleAspectFill will be respected)
+ *
+ * Use -[UIView backgroundColor] to specify color for potential "empty" regions
+ * when UIViewContentModeScaleAspectFit is used.
+ *
+ * @see SINUIViewFullscreenAdditions (SINUIView+Fullscreen.h) for helpers to toggle full screen.
+ */
+- (UIView*)localView;
+
+/**
+ * Set a callback for listening to video frames from a remote stream.
+ *
+ * @param callback The callback object that will receive frames.
+ *
+ * @see SINVideoFrameCallback
+ */
+- (void)setVideoFrameCallback:(id)callback;
+
+/**
+ * Set a callback for listening to video frames captured from the local camera.
+ *
+ * @param callback The callback object that will receive frames.
+ *
+ * @see SINLocalVideoFrameCallback
+ */
+
+- (void)setLocalVideoFrameCallback:(id)callback;
+
+@end
+
+/**
+ * If input position is front-facing camera, returns back-facing camera.
+ * If input position is back-facing camera, returns front-facing camera.
+ * If input is AVCaptureDevicePositionUnspecified, returns input.
+ */
+SIN_EXPORT AVCaptureDevicePosition SINToggleCaptureDevicePosition(AVCaptureDevicePosition position);
+
+/**
+ * Convert a SINVideoFrame to an UIImage.
+ */
+SIN_EXPORT UIImage* SINUIImageFromVideoFrame(id videoFrame);
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINVideoFrame.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINVideoFrame.h
new file mode 100644
index 00000000..677370b0
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINVideoFrame.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+/**
+ * The object for representing a video frame in YUV I420 format.
+ */
+@protocol SINVideoFrame
+
+/** The frame width. */
+@property (readonly) int width;
+
+/** The frame height. */
+@property (readonly) int height;
+
+/**
+ * A method for creating a CVPixelBuffer from the video frame.
+ * The caller of this method takes the ownership of the CVPixelBuffer,
+ * and is responsible for releasing it by calling CVPixelBufferRelease().
+ */
+- (CVPixelBufferRef)createCVPixelBuffer;
+
+/**
+ * A method for releasing the frame data.
+ * Has to be called after the frame callback is done processing the frame.
+ */
+- (void)releaseFrame;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINVideoFrameCallback.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINVideoFrameCallback.h
new file mode 100644
index 00000000..68a5e0f6
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/SINVideoFrameCallback.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+@protocol SINVideoFrame;
+
+/**
+ * The callback object that will process frames from a remote stream.
+ */
+@protocol SINVideoFrameCallback
+
+/**
+ * This method is called when a new frame is received.
+ *
+ * IMPORTANT: The implementor of this protocol is responsible for explicitly
+ * releasing the frame by calling -[SINVideoFrame releaseFrame].
+ *
+ * @param frame The video frame.
+ * @param callId The identifier of the call that received a frame.
+ *
+ * @see @SINVideoFrame
+ */
+- (void)onFrame:(id)videoFrame callId:(NSString*)callId;
+
+@end
+
diff --git a/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/Sinch.h b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/Sinch.h
new file mode 100644
index 00000000..51c46ce5
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/PrivateHeaders/Sinch/Sinch.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+#import "SINExport.h"
+
+#import "SINClient.h"
+#import "SINClientRegistration.h"
+
+#import "SINCallClient.h"
+#import "SINCall.h"
+#import "SINCallDetails.h"
+
+#import "SINMessageClient.h"
+#import "SINMessage.h"
+#import "SINOutgoingMessage.h"
+#import "SINMessageDeliveryInfo.h"
+#import "SINMessageFailureInfo.h"
+
+#import "SINAudioController.h"
+
+#import "SINVideoController.h"
+
+#import "SINPushPair.h"
+#import "SINManagedPush.h"
+#import "SINAPSEnvironment.h"
+#import "SINPushHelper.h"
+
+#import "SINLocalNotification.h"
+#import "SINUILocalNotification+Sinch.h"
+
+#import "SINNotificationResult.h"
+#import "SINCallNotificationResult.h"
+#import "SINMessageNotificationResult.h"
+
+#import "SINLogSeverity.h"
+#import "SINError.h"
+
+/**
+ * The Sinch class is used to instantiate a SINClient.
+ *
+ * This is the starting point for an app that wishes to use the Sinch SDK.
+ *
+ * To construct a SINClient, the required configuration parameters are:
+ *
+ * - Application Key
+ * - Environment host (Production or Sandbox)
+ * - UserID
+ *
+ * It is optional to specify:
+ *
+ * - Application Secret (see the specific factory methods and the User Guide
+ * for details on why and how to use the secret).
+ *
+ * - CLI (Calling-Line Identifier / Caller-ID) that will be used for calls
+ * terminated to PSTN (Publicly Switched Telephone Network).
+ */
+SIN_EXPORT
+@interface Sinch : NSObject
+
+#pragma mark - Basic factory methods
+
+/**
+ * Instantiate a new client.
+ *
+ * If the client is initiated with an application key, but no application
+ * secret, starting the client the first time will require additional
+ * authorization credentials as part of registering the user.
+ * It will therefore be required of the SINClientDelegate to implement
+ * -[SINClientDelegate client:requiresRegistrationCredentials:].
+ *
+ * @return The newly instantiated client.
+ *
+ * @param applicationKey Application key identifying the application.
+ *
+ * @param environmentHost Host for base URL for the Sinch API environment
+ * to be used. E.g. 'sandbox.sinch.com'
+ *
+ *
+ * @param userId ID of the local user
+ *
+ * @see SINClient
+ * @see SINClientRegistration
+ */
+
++ (id)clientWithApplicationKey:(NSString *)applicationKey
+ environmentHost:(NSString *)environmentHost
+ userId:(NSString *)userId;
+
+/**
+ * Instantiate a new client.
+ *
+ * @return The newly instantiated client.
+ *
+ * This method should be used if user-registration and authorization with Sinch
+ * is to be handled completely by the app (without additional involvement
+ * of a backend-service providing additional credentials to the application.)
+ *
+ * @param applicationKey Application key identifying the application.
+ *
+ * @param applicationSecret Application secret bound to application key.
+ *
+ * @param environmentHost Host for base URL for the Sinch API environment
+ * to be used. E.g 'sandbox.sinch.com'
+ *
+ *
+ * @param userId ID of the local user
+ *
+ * @see SINClient
+ */
+
++ (id)clientWithApplicationKey:(NSString *)applicationKey
+ applicationSecret:(NSString *)applicationSecret
+ environmentHost:(NSString *)environmentHost
+ userId:(NSString *)userId;
+
+#pragma mark - Factory methods with support for CLI / PSTN
+
+/**
+ * Instantiate a new client with a CLI (may be used for PSTN-terminated calls).
+ *
+ * If the client is initiated with an application key, but no application
+ * secret, starting the client the first time will require additional
+ * authorization credentials as part of registering the user.
+ * It will therefore be required of the SINClientDelegate to implement
+ * -[SINClientDelegate client:requiresRegistrationCredentials:].
+ *
+ * @return The newly instantiated client.
+ *
+ * @param applicationKey Application key identifying the application.
+ *
+ * @param environmentHost Host for base URL for the Sinch API environment
+ * to be used. E.g. 'sandbox.sinch.com'
+ *
+ *
+ * @param userId ID of the local user
+ *
+ * @param cli Caller-ID when terminating calls to PSTN. Must be a valid phone
+ * number.
+ *
+ * @see SINClient
+ * @see SINClientRegistration
+ */
+
++ (id)clientWithApplicationKey:(NSString *)applicationKey
+ environmentHost:(NSString *)environmentHost
+ userId:(NSString *)userId
+ cli:(NSString *)cli;
+
+/**
+ * Instantiate a new client with a CLI (may be used for PSTN-terminated calls).
+ *
+ * @return The newly instantiated client.
+ *
+ * This method should be used if user-registration and authorization with Sinch
+ * is to be handled completely by the app (without additional involvement
+ * of a backend-service providing additional credentials to the application.)
+ *
+ * @param applicationKey Application key identifying the application.
+ *
+ * @param applicationSecret Application secret bound to application key.
+ *
+ * @param environmentHost Host for base URL for the Sinch API environment
+ * to be used. E.g 'sandbox.sinch.com'
+ *
+ *
+ * @param userId ID of the local user
+ *
+ * @param cli Caller-ID when terminating calls to PSTN. Must be a valid phone
+ * number.
+ *
+ * @see SINClient
+ */
+
++ (id)clientWithApplicationKey:(NSString *)applicationKey
+ applicationSecret:(NSString *)applicationSecret
+ environmentHost:(NSString *)environmentHost
+ userId:(NSString *)userId
+ cli:(NSString *)cli;
+
+/**
+ * Instantiate a new `SINManagedPush` instance to enable Push Notifications
+ * managed by the Sinch SDK and platform. When using managed push notifications,
+ * push notifications will be sent by the Sinch platform provided that Apple
+ * Push Notification Certificates for your application have been uploaded to Sinch.
+ *
+ * @param apsEnvironment Specification of which Apple Push Notification Service environment
+ * the application is bound to (via code signing and Provisioning Profile).
+ *
+ * @see SINAPSEnvironment
+ */
++ (id)managedPushWithAPSEnvironment:(SINAPSEnvironment)apsEnvironment;
+
+/**
+ * Returns the Sinch SDK version.
+ */
++ (NSString *)version;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSClientsObserver.h b/Messenger/Vendors/SinchService/SinchService/SINSClientsObserver.h
new file mode 100644
index 00000000..ac57c185
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSClientsObserver.h
@@ -0,0 +1,20 @@
+#import
+
+// Helper for listening to SINClient NSNotifications
+// Simplifies subscribing to events, and safely unsubscribing.
+
+@protocol SINClient;
+
+typedef void (^SINSClientBlock)(id client);
+typedef void (^SINSClientDidFailBlock)(id client, NSError* error);
+
+@interface SINSClientsObserver : NSObject
+
+@property (nonatomic, copy) SINSClientBlock didStartHandler;
+@property (nonatomic, copy) SINSClientDidFailBlock didFailHandler;
+@property (nonatomic, copy) SINSClientBlock willTerminateHandler;
+
+// array of id
+- (NSArray*)activeClients;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSClientsObserver.m b/Messenger/Vendors/SinchService/SinchService/SINSClientsObserver.m
new file mode 100644
index 00000000..32d38029
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSClientsObserver.m
@@ -0,0 +1,105 @@
+#import "SINSClientsObserver.h"
+#import
+
+// Simple wrapper for holding a weak ref to a client, so we can put this entry into a collection
+@interface SINSClientEntry : NSObject
+@property (nonatomic, weak) id client;
+@end
+@implementation SINSClientEntry
+- (instancetype)initWithClient:(id)client {
+ self = [super init];
+ if (self) {
+ _client = client;
+ }
+ return self;
+}
+@end
+
+@implementation SINSClientsObserver {
+ __strong NSMutableArray *_activeClients;
+}
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ _activeClients = [NSMutableArray array];
+
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc addObserver:self selector:@selector(onClientDidStart:) name:SINClientDidStartNotification object:nil];
+ [nc addObserver:self selector:@selector(onClientDidFail:) name:SINClientDidFailNotification object:nil];
+ [nc addObserver:self selector:@selector(onClientWillTerminate:) name:SINClientWillTerminateNotification object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc removeObserver:self name:SINClientDidStartNotification object:nil];
+ [nc removeObserver:self name:SINClientDidFailNotification object:nil];
+ [nc removeObserver:self name:SINClientWillTerminateNotification object:nil];
+}
+
+- (void)onClientDidStart:(NSNotification *)note {
+ NSParameterAssert(_activeClients);
+ id client = [note object];
+ NSParameterAssert(client);
+ SINSClientEntry *entry = [[SINSClientEntry alloc] initWithClient:client];
+ [_activeClients addObject:entry];
+ if (self.didStartHandler) {
+ self.didStartHandler(client);
+ }
+}
+
+- (void)onClientDidFail:(NSNotification *)note {
+ if (!self.didFailHandler) {
+ return; // no one is interested in this event
+ }
+ id client = [note object];
+ NSError *error = [note userInfo][NSUnderlyingErrorKey];
+ NSParameterAssert(error);
+ for (id entry in [self activeClients]) {
+ if (entry == client) {
+ self.didFailHandler(client, error);
+ }
+ }
+}
+
+- (void)onClientWillTerminate:(NSNotification *)note {
+ NSParameterAssert(_activeClients);
+ id client = [note object];
+
+ // use temporary collection while mutating.
+ id found = nil;
+ NSMutableArray *tmp = [NSMutableArray arrayWithArray:_activeClients];
+ for (SINSClientEntry *entry in tmp) {
+ if ([entry client] == client) {
+ found = entry;
+ break;
+ }
+ }
+ if (found) {
+ [tmp removeObject:found];
+ }
+ NSAssert([_activeClients count] == 0 || [tmp count] == ([_activeClients count] - 1), @"%@",
+ @"inconsistent active clients");
+ _activeClients = tmp;
+
+ if (self.willTerminateHandler) {
+ self.willTerminateHandler(client);
+ }
+}
+
+- (NSArray *)activeClients {
+ NSParameterAssert(_activeClients);
+ NSArray *tmp = [NSArray arrayWithArray:_activeClients];
+ NSMutableArray *retval = [NSMutableArray array];
+ for (SINSClientEntry *entry in tmp) {
+ __strong id client = entry.client;
+ if (entry) {
+ [retval addObject:client];
+ }
+ }
+ return [NSArray arrayWithArray:retval];
+}
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSLazyAudioController.h b/Messenger/Vendors/SinchService/SinchService/SINSLazyAudioController.h
new file mode 100644
index 00000000..7491886a
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSLazyAudioController.h
@@ -0,0 +1,6 @@
+#import
+#import
+#import "SINSLazyProxyBase.h"
+
+@interface SINSLazyAudioController : SINSLazyProxyBase
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSLazyAudioController.m b/Messenger/Vendors/SinchService/SinchService/SINSLazyAudioController.m
new file mode 100644
index 00000000..f3d57a57
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSLazyAudioController.m
@@ -0,0 +1,59 @@
+#import "SINSLazyAudioController.h"
+
+#define WARN_AND_RETURN_ON_NO_PROXEE() \
+ if (![self proxee]) { \
+ [self logNoProxeeAvailable]; \
+ return; \
+ }
+
+@implementation SINSLazyAudioController
+
+- (void)dealloc {
+ if (self.proxee) {
+ [self willSetProxeeToNil:self.proxee];
+ }
+}
+
+- (void)willSetProxeeToNil:(id)proxee {
+ if ([proxee respondsToSelector:@selector(invalidate)]) {
+ [proxee invalidate];
+ }
+}
+
+- (void)logNoProxeeAvailable {
+ NSLog(@"WARNING: No underlying SINAudioController available");
+}
+
+#pragma mark - SINAudioController
+
+- (void)unmute {
+ WARN_AND_RETURN_ON_NO_PROXEE();
+ [self.proxee unmute];
+}
+
+- (void)mute {
+ WARN_AND_RETURN_ON_NO_PROXEE();
+ [self.proxee mute];
+}
+
+- (void)startPlayingSoundFile:(NSString *)path loop:(BOOL)loop {
+ WARN_AND_RETURN_ON_NO_PROXEE();
+ [self.proxee startPlayingSoundFile:path loop:loop];
+}
+
+- (void)enableSpeaker {
+ WARN_AND_RETURN_ON_NO_PROXEE();
+ [self.proxee enableSpeaker];
+}
+
+- (void)disableSpeaker {
+ WARN_AND_RETURN_ON_NO_PROXEE();
+ [self.proxee disableSpeaker];
+}
+
+- (void)stopPlayingSoundFile {
+ WARN_AND_RETURN_ON_NO_PROXEE();
+ [self.proxee stopPlayingSoundFile];
+}
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSLazyCallClient.h b/Messenger/Vendors/SinchService/SinchService/SINSLazyCallClient.h
new file mode 100644
index 00000000..e1823ccf
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSLazyCallClient.h
@@ -0,0 +1,6 @@
+#import
+#import
+#import "SINSLazyProxyBase.h"
+
+@interface SINSLazyCallClient : SINSLazyProxyBase
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSLazyCallClient.m b/Messenger/Vendors/SinchService/SinchService/SINSLazyCallClient.m
new file mode 100644
index 00000000..d736ea9a
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSLazyCallClient.m
@@ -0,0 +1,114 @@
+#import "SINSLazyCallClient.h"
+#import "SINServiceError.h"
+#import
+
+// SINFailedCall represents an immediately failed call. E.g. when trying to initiate a call before a SINClient is
+// completely initialized and started.
+
+@interface SINSFailedCall : NSObject
+@property (nonatomic, readonly, copy) NSString *remoteUserId;
+// SINCallDetails
+@property (nonatomic, readonly, strong) NSDate *startedTime;
+@property (nonatomic, readonly, strong) NSDate *establishedTime;
+@property (nonatomic, readonly, strong) NSDate *endedTime;
+@property (nonatomic, readonly) UIApplicationState applicationStateWhenReceived;
+@end
+
+@implementation SINSFailedCall
+
+@synthesize userInfo = _userInfo; // -[SINCall userInfo]
+@synthesize headers = _headers;
+@synthesize delegate = _delegate;
+
+- (instancetype)init {
+ [NSException raise:NSInternalInconsistencyException format:@"Use designated initializer"];
+ return nil;
+}
+
+- (instancetype)initWithUserId:(NSString *)userId headers:(NSDictionary *)headers {
+ self = [super init];
+ if (self) {
+ _remoteUserId = userId;
+ _headers = headers;
+ _startedTime = [NSDate date];
+ _endedTime = _startedTime;
+ _establishedTime = nil;
+ _applicationStateWhenReceived = [[UIApplication sharedApplication] applicationState];
+ }
+ return self;
+}
+
+- (NSString *)callId {
+ return @"";
+}
+
+- (SINCallState)state {
+ return SINCallStateEnded;
+}
+
+- (SINCallDirection)direction {
+ return SINCallDirectionOutgoing;
+}
+
+- (id)details {
+ return self;
+}
+
+- (void)setDelegate:(id)delegate {
+ _delegate = delegate;
+ if (_delegate) {
+ [_delegate callDidEnd:self];
+ }
+}
+
+- (void)sendDTMF:(NSString *)key {
+ // noop
+}
+
+- (void)hangup {
+ // noop
+}
+
+- (void)answer {
+ // noop
+}
+
+#pragma mark - SINCallDetails
+
+- (SINCallEndCause)endCause {
+ return SINCallEndCauseError;
+}
+
+- (NSError *)error {
+ return SINServiceComponentNotAvailableError();
+}
+
+@end
+
+@implementation SINSLazyCallClient
+
+- (id)callUserWithId:(NSString *)userId {
+ return [self callUserWithId:userId headers:@{}];
+}
+
+- (id)callUserWithId:(NSString *)userId headers:(NSDictionary *)headers {
+ if (self.proxee) {
+ return [self.proxee callUserWithId:userId headers:headers];
+ } else {
+ return [[SINSFailedCall alloc] initWithUserId:userId headers:headers];
+ }
+}
+
+- (id)callPhoneNumber:(NSString *)phoneNumber {
+ return [self callPhoneNumber:phoneNumber headers:@{}];
+}
+
+- (id)callPhoneNumber:(NSString *)phoneNumber headers:(NSDictionary *)headers {
+ if (self.proxee) {
+ return [self.proxee callPhoneNumber:phoneNumber headers:headers];
+ } else {
+ return [[SINSFailedCall alloc] initWithUserId:phoneNumber headers:headers];
+ }
+}
+
+@end
\ No newline at end of file
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSLazyMessageClient.h b/Messenger/Vendors/SinchService/SinchService/SINSLazyMessageClient.h
new file mode 100644
index 00000000..41c9f130
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSLazyMessageClient.h
@@ -0,0 +1,7 @@
+#import
+#import
+#import "SINSLazyProxyBase.h"
+
+@interface SINSLazyMessageClient : SINSLazyProxyBase
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSLazyMessageClient.m b/Messenger/Vendors/SinchService/SinchService/SINSLazyMessageClient.m
new file mode 100644
index 00000000..9a578dc8
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSLazyMessageClient.m
@@ -0,0 +1,78 @@
+#import "SINSLazyMessageClient.h"
+#import
+#import "SINSParameterValidation.h"
+#import "SINServiceError.h"
+
+@interface SINSMessageFailureInfo : NSObject
+@property (nonatomic, readwrite, copy) NSString *messageId;
+@property (nonatomic, readwrite, copy) NSString *recipientId;
+@property (nonatomic, readwrite, copy) NSError *error;
+@end
+
+@implementation SINSMessageFailureInfo
+@end
+
+@interface SINSFailedOutgoingMessage : NSObject
+@property (nonatomic, readonly) NSDate *timestamp;
+@end
+
+@implementation SINSFailedOutgoingMessage {
+ __strong SINOutgoingMessage *_message;
+}
+
+- (instancetype)initWithMessage:(SINOutgoingMessage *)message {
+ self = [super init];
+ if (self) {
+ _timestamp = [NSDate date];
+ _message = message;
+ }
+ return self;
+}
+
+- (NSString *)messageId {
+ return [_message messageId];
+}
+
+- (NSArray *)recipientIds {
+ return [_message recipientIds];
+}
+
+- (NSString *)senderId {
+ return @""; // unknown because SINClient not created yet
+}
+
+- (NSString *)text {
+ return [_message text];
+}
+
+- (NSDictionary *)headers {
+ return [_message headers];
+}
+
+@end
+
+@implementation SINSLazyMessageClient
+
+- (void)sendMessage:(SINOutgoingMessage *)message {
+ SINSParameterCondition(message);
+
+ if (self.proxee) {
+ [self.proxee sendMessage:message];
+ } else {
+ [self failMessage:message];
+ }
+}
+
+- (void)failMessage:(SINOutgoingMessage *)message {
+ NSParameterAssert(message);
+
+ for (NSString *recipientId in [message recipientIds]) {
+ SINSMessageFailureInfo *info = [[SINSMessageFailureInfo alloc] init];
+ info.messageId = [message messageId];
+ info.recipientId = recipientId;
+ info.error = SINServiceComponentNotAvailableError();
+ [self.delegate messageFailed:[[SINSFailedOutgoingMessage alloc] initWithMessage:message] info:info];
+ }
+}
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSLazyProxyBase.h b/Messenger/Vendors/SinchService/SinchService/SINSLazyProxyBase.h
new file mode 100644
index 00000000..e34a5fc9
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSLazyProxyBase.h
@@ -0,0 +1,10 @@
+#import
+
+@interface SINSLazyProxyBase : NSObject
+
+@property (nonatomic, strong) id proxee;
+@property (nonatomic, weak) id delegate; // support if proxee can have a delegate
+
+- (void)willSetProxeeToNil:(id)proxee; // subclass override hook
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSLazyProxyBase.m b/Messenger/Vendors/SinchService/SinchService/SINSLazyProxyBase.m
new file mode 100644
index 00000000..881991fe
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSLazyProxyBase.m
@@ -0,0 +1,34 @@
+#import "SINSLazyProxyBase.h"
+
+@implementation SINSLazyProxyBase
+
+- (void)assignDelegateToProxee:(id)delegate {
+ if ([_proxee respondsToSelector:@selector(setDelegate:)]) {
+ [_proxee setDelegate:delegate];
+ }
+}
+
+- (void)setDelegate:(id)delegate {
+ _delegate = delegate;
+ [self assignDelegateToProxee:_delegate];
+}
+
+- (void)setProxee:(id)proxee {
+ if (_proxee && (nil == proxee)) {
+ [self willSetProxeeToNil:_proxee];
+ }
+ _proxee = proxee;
+ if (_delegate) {
+ [self assignDelegateToProxee:_delegate];
+ } else {
+ if ([_proxee respondsToSelector:@selector(delegate)]) {
+ _delegate = [_proxee delegate];
+ }
+ }
+}
+
+- (void)willSetProxeeToNil:(id)proxee {
+ // noop, subclass override
+}
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSParameterValidation.h b/Messenger/Vendors/SinchService/SinchService/SINSParameterValidation.h
new file mode 100644
index 00000000..d3644215
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSParameterValidation.h
@@ -0,0 +1,8 @@
+#import
+
+#define SINSParameterCondition(_condition_) \
+ if (!_condition_) { \
+ @throw [NSException exceptionWithName:NSInvalidArgumentException \
+ reason:[NSString stringWithFormat:@"Parameter '%s' is invalid", #_condition_] \
+ userInfo:nil]; \
+ }
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSServicePersistence.h b/Messenger/Vendors/SinchService/SinchService/SINSServicePersistence.h
new file mode 100644
index 00000000..3f26317d
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSServicePersistence.h
@@ -0,0 +1,15 @@
+#import
+
+// Persistence for high-level SINService
+
+@class SINServiceConfig;
+
+@interface SINSServicePersistence : NSObject
+
+- (instancetype)initWithConfig:(SINServiceConfig *)config;
+
+- (id)objectForKey:(NSString *)key;
+- (void)setObject:(id)value forKey:(NSString *)key;
+- (void)removeObjectForKey:(NSString *)key;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINSServicePersistence.m b/Messenger/Vendors/SinchService/SinchService/SINSServicePersistence.m
new file mode 100644
index 00000000..7a5a3a78
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINSServicePersistence.m
@@ -0,0 +1,89 @@
+#import "SINSServicePersistence.h"
+#import "SINService.h" // for SINServiceConfig
+
+static NSString *const SINSServicePersistenceGlobalRootKey = @"SINSServicePersistenceGlobalRoot";
+
+@interface SINServiceConfig (ApplicationKey)
+- (NSString *)applicationKey;
+@end
+
+@implementation SINSServicePersistence {
+ SINServiceConfig *_config;
+ NSUserDefaults *_persistence;
+}
+
+- (instancetype)initWithConfig:(SINServiceConfig *)config {
+ NSParameterAssert(config);
+ self = [super init];
+ if (self) {
+ _config = config;
+ _persistence = [NSUserDefaults standardUserDefaults];
+ }
+ return self;
+}
+
+- (NSString *)applicationKey {
+ NSString *appKey = [_config applicationKey];
+ NSAssert([appKey length], @"%@", @"");
+ if ([appKey length] == 0) {
+ [NSException raise:NSInternalInconsistencyException format:@"Invalid Sinch application key"];
+ }
+ return appKey;
+}
+
+- (NSUserDefaults *)persistence {
+ NSParameterAssert(_persistence);
+ return _persistence;
+}
+
+- (void)synchronize {
+ // NSUserDefaults is thread safe, so lets dispatch to non-main thread
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self.persistence synchronize]; });
+}
+
+- (NSDictionary *)globalRoot {
+ NSDictionary *root = [self.persistence objectForKey:SINSServicePersistenceGlobalRootKey];
+ if (root) {
+ return root;
+ }
+ return [NSDictionary dictionary];
+}
+
+- (NSDictionary *)root {
+ NSDictionary *appKeyRoot = [self.globalRoot objectForKey:self.applicationKey];
+ if (appKeyRoot) {
+ return appKeyRoot;
+ }
+ return [NSDictionary dictionary];
+}
+
+- (void)writeRoot:(NSDictionary *)appKeySpace {
+ NSParameterAssert(appKeySpace);
+ NSMutableDictionary *tmp = [NSMutableDictionary dictionaryWithDictionary:self.globalRoot];
+ [tmp setObject:appKeySpace forKey:self.applicationKey];
+ [self.persistence setObject:tmp forKey:SINSServicePersistenceGlobalRootKey];
+}
+
+- (id)objectForKey:(NSString *)key {
+ NSParameterAssert(key);
+ return [[self root] objectForKey:key];
+}
+
+- (void)setObject:(id)value forKey:(NSString *)key {
+ NSParameterAssert(value);
+ NSParameterAssert(key);
+ NSMutableDictionary *tmp = [NSMutableDictionary dictionaryWithDictionary:[self root]];
+ [tmp setObject:value forKey:key];
+ [self writeRoot:tmp];
+ [self synchronize];
+}
+
+- (void)removeObjectForKey:(NSString *)key {
+ NSParameterAssert(key);
+ NSMutableDictionary *tmp = [NSMutableDictionary dictionaryWithDictionary:[self root]];
+ [tmp removeObjectForKey:key];
+ [self writeRoot:tmp];
+ [self synchronize];
+}
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINService.h b/Messenger/Vendors/SinchService/SinchService/SINService.h
new file mode 100644
index 00000000..54ced50f
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINService.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import
+
+#import
+#import
+#import
+
+#pragma mark - SINServiceConfig
+
+// SINServiceConfig is used when creating a SINService
+//
+// A config will by default have the following features and behaviour specified:
+//
+// - Calling is enabled
+// - Instant Messaging is enabled
+// - Active Connection is enabled (but not when the application moves to background)
+//
+// It is recommended to enable support for remote push notifications by using
+// -[SINServiceConfig pushNotificationsWithEnvironment:].
+//
+
+@interface SINServiceConfig : NSObject
+
+- (instancetype)initWithApplicationKey:(NSString *)applicationKey
+ applicationSecret:(NSString *)applicationSecret
+ environmentHost:(NSString *)environmentHost;
+
+- (instancetype)initWithApplicationKey:(NSString *)applicationKey environmentHost:(NSString *)environmentHost;
+
+// Enable use of Apple Remote Push Notifications
+// (this is a chainable mutator, returns self)
+- (instancetype)pushNotificationsWithEnvironment:(SINAPSEnvironment)apsEnvironment;
+
+// Disable Calling Feature
+// (this is a chainable mutator, returns self)
+- (instancetype)disableCalling;
+
+// Disable Instant Messaging
+// (this is a chainable mutator, returns self)
+- (instancetype)disableMessaging;
+
+// Maps to -[SINClient setSupportActiveConnectionInBackground:]
+- (instancetype)enableActiveConnectionInBackground;
+
+// The SINService will by default invoke -[SINClient startListeningOnActiveConnection]
+// when starting a Sinch client. This behaviour can be disabled via this method.
+// If active connection is disabled, remote push notifications should be used (see
+// -[SINServiceConfig pushNotificationsWithEnvironment:]).
+- (instancetype)disableActiveConnection;
+
+@end
+
+#pragma mark - SINService
+
+@protocol SINServiceDelegate;
+
+@protocol SINService
+
+@property (nonatomic, readwrite, weak) id delegate;
+
+- (NSString *)userId; // currently active userId. may be nil
+
+- (void)logInUserWithId:(NSString *)userId;
+- (void)logOutUser;
+
+- (id)callClient;
+
+- (id)messageClient;
+
+- (id)client;
+
+- (id)push;
+
+- (id)audioController;
+
+@end
+
+#pragma mark - SINServiceDelegate
+
+@protocol SINServiceDelegate
+
+@optional
+
+- (void)service:(id)service didFailWithError:(NSError *)error;
+
+- (void)service:(id)service
+ logMessage:(NSString *)message
+ area:(NSString *)area
+ severity:(SINLogSeverity)severity
+ timestamp:(NSDate *)timestamp;
+
+- (void)service:(id)service requiresRegistrationCredentials:(id)registrationCallback;
+
+// Delegate is notified of the notification result _after_ it's been relayed to the underlying SINClient.
+- (void)service:(id)service didReceiveNotification:(id)notificationResult;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINService.m b/Messenger/Vendors/SinchService/SinchService/SINService.m
new file mode 100644
index 00000000..0bc75000
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINService.m
@@ -0,0 +1,356 @@
+#import "SINService.h"
+#import
+#import "SINSLazyCallClient.h"
+#import "SINSLazyAudioController.h"
+#import "SINSLazyMessageClient.h"
+#import "SINSClientsObserver.h"
+#import "SINSServicePersistence.h"
+#import "SINSParameterValidation.h"
+#import "SINServiceError.h"
+
+static NSString *const SINServiceUserIdKey = @"userId";
+
+@interface SINServiceConfig ()
+@property (nonatomic, copy, readwrite) NSString *applicationKey;
+@property (nonatomic, copy, readwrite) NSString *applicationSecret;
+@property (nonatomic, copy, readwrite) NSString *environmentHost;
+@property (nonatomic, assign) BOOL calling;
+@property (nonatomic, assign) BOOL messaging;
+@property (nonatomic, assign) BOOL managedPushNotifications;
+@property (nonatomic, assign) SINAPSEnvironment apsEnvironment;
+@property (nonatomic, assign) BOOL activeConnection; // maps to -[SINClient startListeningOnActiveConnection]
+@property (nonatomic, assign) BOOL activeConnectionInBackground;
+@end
+
+@implementation SINServiceConfig
+
+- (instancetype)init {
+ [NSException raise:NSInternalInconsistencyException format:@"Use designated initializer"];
+ return nil;
+}
+
+- (instancetype)initWithApplicationKey:(NSString *)applicationKey environmentHost:(NSString *)environmentHost {
+ return [self initWithApplicationKey:applicationKey applicationSecret:@"" environmentHost:environmentHost];
+}
+- (instancetype)initWithApplicationKey:(NSString *)applicationKey
+ applicationSecret:(NSString *)applicationSecret
+ environmentHost:(NSString *)environmentHost {
+ self = [super init];
+ if (self) {
+ _applicationKey = [applicationKey copy];
+ _environmentHost = [environmentHost copy];
+ _applicationSecret = [applicationSecret copy];
+
+ // Feature defaults
+ _calling = YES;
+ _messaging = YES;
+ _activeConnection = YES;
+ }
+ return self;
+}
+
++ (instancetype)config {
+ return [[SINServiceConfig alloc] init];
+}
+
+- (instancetype)applicationKey:(NSString *)applicationKey {
+ self.applicationKey = applicationKey;
+ return self;
+}
+
+- (instancetype)environmentHost:(NSString *)environmentHost {
+ self.environmentHost = environmentHost;
+ return self;
+}
+
+- (instancetype)disableCalling {
+ self.calling = NO;
+ return self;
+}
+
+- (instancetype)disableMessaging {
+ self.messaging = NO;
+ return self;
+}
+
+- (instancetype)pushNotificationsWithEnvironment:(SINAPSEnvironment)apsEnvironment {
+ self.managedPushNotifications = YES;
+ self.apsEnvironment = apsEnvironment;
+ return self;
+}
+
+- (instancetype)enableActiveConnectionInBackground {
+ self.activeConnectionInBackground = YES;
+ return self;
+}
+
+- (instancetype)disableActiveConnection {
+ self.activeConnection = NO;
+ return self;
+}
+
+@end
+
+#pragma mark -
+
+@interface SINService : NSObject
+@property (nonatomic, strong, readonly) SINServiceConfig *config;
+@property (nonatomic, strong, readonly) SINSServicePersistence *persistence;
+@property (nonatomic, strong, readonly) SINSClientsObserver *clientsObserver;
+@property (nonatomic, strong, readwrite) id client;
+@property (nonatomic, strong, readwrite) id push;
+@property (nonatomic, strong, readonly) SINSLazyCallClient *callClient;
+@property (nonatomic, strong, readonly) SINSLazyMessageClient *messageClient;
+@property (nonatomic, strong, readonly) SINSLazyAudioController *audioController;
+@end
+
+@implementation SINService {
+ BOOL _delegateRespondsToLogCallback;
+}
+
+@synthesize delegate = _delegate;
+
+- (instancetype)init {
+ [NSException raise:NSInternalInconsistencyException format:@"Use designated initializer"];
+ return nil;
+}
+
+- (instancetype)initWithConfig:(SINServiceConfig *)config {
+ SINSParameterCondition(config);
+ SINSParameterCondition(config.applicationKey);
+ SINSParameterCondition(config.environmentHost);
+ // applicationSecret is optional
+
+ self = [super init];
+ if (self) {
+ _config = config;
+ _persistence = [[SINSServicePersistence alloc] initWithConfig:_config];
+
+ {
+ _clientsObserver = [[SINSClientsObserver alloc] init];
+ __weak id weakSelf = self;
+ _clientsObserver.didStartHandler = ^(id client) {
+ [weakSelf onClientDidStart:client];
+ };
+ _clientsObserver.willTerminateHandler = ^(id client) {
+ [weakSelf onClientWillTerminate:client];
+ };
+ _clientsObserver.didFailHandler = ^(id client, NSError *error) {
+ [weakSelf onClientDidFail:client error:error];
+ };
+ }
+
+ if (_config.managedPushNotifications) {
+ _push = [Sinch managedPushWithAPSEnvironment:_config.apsEnvironment];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(onDidReceiveRemoteNotification:)
+ name:SINApplicationDidReceiveRemoteNotification
+ object:_push];
+ [_push setDesiredPushTypeAutomatically];
+ }
+
+ _callClient = [[SINSLazyCallClient alloc] init];
+ _messageClient = [[SINSLazyMessageClient alloc] init];
+ _audioController = [[SINSLazyAudioController alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if (_push) {
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:SINApplicationDidReceiveRemoteNotification
+ object:_push];
+ }
+}
+
+- (id)push {
+ if (!_config.managedPushNotifications) {
+ [NSException raise:NSInternalInconsistencyException
+ format:@"enableManagedPushNotifications: was not configured for %@",
+ NSStringFromClass([SINServiceConfig class])];
+ }
+ return _push;
+}
+
+- (NSString *)userId {
+ if (self.client) {
+ return [self.client userId];
+ }
+ return nil;
+}
+
+- (void)logInUserWithId:(NSString *)userId {
+ SINSParameterCondition(userId);
+
+ if (self.client) {
+ if ([[self.client userId] isEqualToString:userId]) {
+ return;
+ }
+ [self.client terminate];
+ self.client = nil;
+ }
+
+ SINServiceConfig *cfg = [self config];
+ NSParameterAssert(cfg);
+
+ id client;
+ if ([cfg.applicationSecret length] > 0) {
+ client = [Sinch clientWithApplicationKey:cfg.applicationKey
+ applicationSecret:cfg.applicationSecret
+ environmentHost:cfg.environmentHost
+ userId:userId];
+
+ } else {
+ client = [Sinch clientWithApplicationKey:cfg.applicationKey environmentHost:cfg.environmentHost userId:userId];
+ }
+
+ client.delegate = self;
+
+ [client setSupportMessaging:cfg.messaging];
+ [client setSupportCalling:cfg.calling];
+
+ if (cfg.managedPushNotifications) {
+ [client enableManagedPushNotifications];
+ }
+
+ [client setSupportActiveConnectionInBackground:cfg.activeConnectionInBackground];
+
+ self.client = client;
+
+ // Assign proxees before calling -[SINClient start], so that delegates are properly transferred / assigned
+ // if needed throughout the startup phase. E.g. if a client is started as a consequence of receiving a
+ // incoming remote push notification, the call client delegate must be assigned to be able to schedule a local
+ // notification for the call.
+ [_callClient setProxee:[client callClient]];
+ [_audioController setProxee:[client audioController]];
+ [_messageClient setProxee:[client messageClient]];
+
+ [self.client start];
+
+ if (cfg.activeConnection) {
+ [self.client startListeningOnActiveConnection];
+ }
+}
+
+- (void)logInLastKnownUser {
+ if ([[self lastKnownUserId] length]) {
+ [self logInLastKnownUserIfPossible];
+ } else {
+ NSError *error = SINServiceCreateError(SINServiceErrorUserIdNotAvailable, @"No persisted UserId is available");
+ [self notifyError:error];
+ }
+}
+
+- (NSString *)lastKnownUserId {
+ NSString *userId = [self.persistence objectForKey:SINServiceUserIdKey];
+ if ([userId length]) {
+ return userId;
+ }
+ return nil;
+}
+
+- (void)logInLastKnownUserIfPossible {
+ NSString *userId = [self lastKnownUserId];
+ if ([userId length]) {
+ [self logInUserWithId:userId];
+ }
+}
+
+- (void)logOutUser {
+ [self.persistence removeObjectForKey:SINServiceUserIdKey];
+
+ if (self.client) {
+ [self.client unregisterPushNotificationDeviceToken];
+ [self.client terminateGracefully];
+ self.client = nil;
+ }
+}
+
+- (void)setDelegate:(id)delegate {
+ _delegate = delegate;
+ _delegateRespondsToLogCallback =
+ [_delegate respondsToSelector:@selector(service:logMessage:area:severity:timestamp:)];
+}
+
+#pragma mark - SINClientDelegate
+
+- (void)clientDidStart:(id)client {
+ // noop, we use onClientDidStart: via NSNotification
+}
+
+- (void)clientDidFail:(id)client error:(NSError *)error {
+ // noop, we use onClientDidFail:error: via NSNotification
+}
+
+- (void)client:(id)client requiresRegistrationCredentials:(id)registrationCallback {
+ if ([self.delegate respondsToSelector:@selector(service:requiresRegistrationCredentials:)]) {
+ [self.delegate service:self requiresRegistrationCredentials:registrationCallback];
+ } else {
+ NSLog(@"WARNING: no delegate assigned to handle SINClient authorization");
+ }
+}
+
+- (void)client:(id)client
+ logMessage:(NSString *)message
+ area:(NSString *)area
+ severity:(SINLogSeverity)severity
+ timestamp:(NSDate *)timestamp {
+ if (_delegateRespondsToLogCallback) {
+ [self.delegate service:self logMessage:message area:area severity:severity timestamp:timestamp];
+ }
+}
+
+#pragma mark - SINClientsObserver handlers
+
+- (void)onClientDidStart:(id)client {
+ if (client == self.client) {
+ // persist last known successfully logged in user
+ [self.persistence setObject:client.userId forKey:SINServiceUserIdKey];
+ }
+}
+
+- (void)onClientWillTerminate:(id)client {
+ if (client == self.client) {
+ [_callClient setProxee:nil];
+ [_messageClient setProxee:nil];
+ [_audioController setProxee:nil];
+ }
+}
+
+- (void)onClientDidFail:(id)client error:(NSError *)error {
+ if (client == self.client) {
+ [self notifyError:error];
+ }
+}
+
+- (void)notifyError:(NSError *)error {
+ NSParameterAssert(error);
+ if ([self.delegate respondsToSelector:@selector(service:didFailWithError:)]) {
+ [self.delegate service:self didFailWithError:error];
+ }
+}
+
+#pragma mark -
+
+- (void)onDidReceiveRemoteNotification:(NSNotification *)note {
+ NSAssert([note object] == _push, @"%@", @"");
+ if ([note object] == _push) {
+ NSDictionary *dictionaryPayload = note.userInfo[SINRemoteNotificationKey];
+ if ([dictionaryPayload sin_isSinchPushPayload]) {
+ if (!self.client) {
+ [self logInLastKnownUserIfPossible];
+ }
+
+ // If we had a client since before, or if logInLastKnownUserIfPossible succeded
+ if (self.client) {
+ id result = [self.client relayRemotePushNotification:dictionaryPayload];
+ if (result && [self.delegate respondsToSelector:@selector(service:didReceiveNotification:)]) {
+ [self.delegate service:self didReceiveNotification:result];
+ }
+ }
+ } /* else: was not a Sinch push at all? */
+ }
+}
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SINServiceError.h b/Messenger/Vendors/SinchService/SinchService/SINServiceError.h
new file mode 100644
index 00000000..4216db00
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINServiceError.h
@@ -0,0 +1,11 @@
+#import
+
+extern NSString* const SINServiceErrorDomain;
+
+typedef NS_ENUM(NSInteger, SINServiceError) {
+ SINServiceErrorComponentNotAvailable = 1,
+ SINServiceErrorUserIdNotAvailable
+};
+
+extern NSError* SINServiceComponentNotAvailableError(void);
+extern NSError* SINServiceCreateError(SINServiceError code, NSString* reason);
diff --git a/Messenger/Vendors/SinchService/SinchService/SINServiceError.m b/Messenger/Vendors/SinchService/SinchService/SINServiceError.m
new file mode 100644
index 00000000..4fe51986
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SINServiceError.m
@@ -0,0 +1,17 @@
+#import "SINServiceError.h"
+#import "SINSParameterValidation.h"
+
+NSString *const SINServiceErrorDomain = @"SINServiceErrorDomain";
+
+NSError *SINServiceComponentNotAvailableError(void) {
+ return SINServiceCreateError(SINServiceErrorComponentNotAvailable, @"SINClient is not started");
+}
+
+NSError *SINServiceCreateError(SINServiceError code, NSString *reason) {
+ SINSParameterCondition(reason);
+ if (!reason) {
+ reason = @"";
+ }
+ NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey : reason, NSLocalizedDescriptionKey : reason};
+ return [NSError errorWithDomain:SINServiceErrorDomain code:code userInfo:userInfo];
+}
diff --git a/Messenger/Vendors/SinchService/SinchService/SinchService.h b/Messenger/Vendors/SinchService/SinchService/SinchService.h
new file mode 100644
index 00000000..18fb2ff9
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SinchService.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2015 Sinch AB. All rights reserved.
+ *
+ * See LICENSE file for license terms and information.
+ */
+
+#import "SINService.h"
+
+@interface SinchService : NSObject
+
++ (SINServiceConfig *)configWithApplicationKey:(NSString *)applicationKey
+ applicationSecret:(NSString *)applicationSecret
+ environmentHost:(NSString *)environmentHost;
+
++ (id)serviceWithConfig:(SINServiceConfig *)config;
+
+@end
diff --git a/Messenger/Vendors/SinchService/SinchService/SinchService.m b/Messenger/Vendors/SinchService/SinchService/SinchService.m
new file mode 100644
index 00000000..46cab428
--- /dev/null
+++ b/Messenger/Vendors/SinchService/SinchService/SinchService.m
@@ -0,0 +1,23 @@
+#import "SinchService.h"
+#import "SINService.h"
+
+@protocol SINServicePrivate
+- (id)initWithConfig:(SINServiceConfig *)config;
+@end
+
+@implementation SinchService
+
++ (id)serviceWithConfig:(SINServiceConfig *)config {
+ Class klazz = NSClassFromString(@"SINService");
+ return [[klazz alloc] initWithConfig:config];
+}
+
++ (SINServiceConfig *)configWithApplicationKey:(NSString *)applicationKey
+ applicationSecret:(NSString *)applicationSecret
+ environmentHost:(NSString *)environmentHost {
+ return [[SINServiceConfig alloc] initWithApplicationKey:applicationKey
+ applicationSecret:applicationSecret
+ environmentHost:environmentHost];
+}
+
+@end
diff --git a/Messenger/app-Bridging-Header.h b/Messenger/app-Bridging-Header.h
new file mode 100644
index 00000000..64014c29
--- /dev/null
+++ b/Messenger/app-Bridging-Header.h
@@ -0,0 +1,38 @@
+//
+// Use this file to import your target's public headers that you would like to expose to Swift.
+//
+
+// -----------------------------------------------------------------------------
+// Begin Swiftify generated imports
+
+// NOTE:
+// 1. Put your custom `#import` directives outside of this section to avoid them being overwritten.
+// 2. To use your Objective-C code from Swift:
+// • Add `import MyObjcClass` to your .swift file(s) depending on the Objective-C code;
+// • Ensure that `#import MyObjcClass.h` is present in `app-Bridging-Header.h`.
+// 3. To use your Swift code from Objective-C:
+// • Add `@class MySwiftClass` to your .h files that depend on the Swift code;
+// • No need to import the Swift Bridging Header (`app-Swift.h`), since it's already being imported fom the .pch file.
+
+#import "AppConstant.h"
+#import "utilities.h"
+
+// End Swiftify generated imports
+// -----------------------------------------------------------------------------
+
+//-------------------------------------------------------------------------------------------------------------------------------------------------
+#define RC_TYPE_STATUS 1
+#define RC_TYPE_TEXT 2
+#define RC_TYPE_EMOJI 3
+#define RC_TYPE_PICTURE 4
+#define RC_TYPE_VIDEO 5
+#define RC_TYPE_AUDIO 6
+#define RC_TYPE_LOCATION 7
+//---------------------------------------------------------------------------------
+#define RC_STATUS_LOADING 1
+#define RC_STATUS_SUCCEED 2
+#define RC_STATUS_MANUAL 3
+//---------------------------------------------------------------------------------
+#define RC_AUDIOSTATUS_STOPPED 1
+#define RC_AUDIOSTATUS_PLAYING 2
+//-------------------------------------------------------------------------------------------------------------------------------------------------
diff --git a/Messenger/app-Prefix.pch b/Messenger/app-Prefix.pch
new file mode 100644
index 00000000..a207b1c9
--- /dev/null
+++ b/Messenger/app-Prefix.pch
@@ -0,0 +1,9 @@
+//
+// Prefix header for all source files of all targets in the '0' project.
+//
+#ifdef __OBJC__
+ // Added by Swiftify: import the Swift Bridging Header from the .pch file
+ // to make it accessible from all Objective-C source files
+ #import
+
+#endif
\ No newline at end of file
diff --git a/Messenger/app.xcodeproj/project.pbxproj b/Messenger/app.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..38e30fe3
--- /dev/null
+++ b/Messenger/app.xcodeproj/project.pbxproj
@@ -0,0 +1,3334 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 290D1A081F8BCDCB00CFBFB0 /* DBBlocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290D1A071F8BCDCB00CFBFB0 /* DBBlocker.swift */; };
+ 290D1A0B1F8BCEB800CFBFB0 /* Blockers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290D1A0A1F8BCEB800CFBFB0 /* Blockers.swift */; };
+ 290D1A0E1F8BD39200CFBFB0 /* Blocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290D1A0C1F8BD39100CFBFB0 /* Blocker.swift */; };
+ 29104BC020EFC21B003BD623 /* RCAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29104BBE20EFC21B003BD623 /* RCAudioPlayer.swift */; };
+ 2917DE1A20ECA4D500EBCD97 /* NSDictionary+Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2917DE1920ECA4D500EBCD97 /* NSDictionary+Util.swift */; };
+ 291CB49B202F7E460078D0B0 /* chat_audio@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 291CB49A202F7E460078D0B0 /* chat_audio@2x.png */; };
+ 291F172F1DCCF60E004CA70F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 297DBA0A1DCB5F13008551F2 /* PushKit.framework */; };
+ 2920B1882030AD3500DB6E07 /* AudioView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2920B1822030AD3500DB6E07 /* AudioView.xib */; };
+ 2920B18B2030AD3500DB6E07 /* AudioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2920B1872030AD3500DB6E07 /* AudioView.swift */; };
+ 2921ADC01F8BA13500B70B2F /* DBFriend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2921ADBE1F8BA13500B70B2F /* DBFriend.swift */; };
+ 2921ADC31F8BA1E700B70B2F /* Friends.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2921ADC11F8BA1E700B70B2F /* Friends.swift */; };
+ 292BBFE120E179EE003FE30C /* CallAudioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292BBFDF20E179EE003FE30C /* CallAudioView.swift */; };
+ 2931FF1120ECDFC5002C0A97 /* PictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931FF0F20ECDFC5002C0A97 /* PictureView.swift */; };
+ 2940548F1F9BCF8600D03A43 /* LinkedId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2940548D1F9BCF8600D03A43 /* LinkedId.swift */; };
+ 294E496A1F8A6AA800045345 /* AddFriendsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294E49641F8A6AA700045345 /* AddFriendsView.swift */; };
+ 294E496B1F8A6AA800045345 /* AddFriendsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 294E49651F8A6AA700045345 /* AddFriendsCell.xib */; };
+ 294E496C1F8A6AA800045345 /* AddFriendsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 294E49661F8A6AA700045345 /* AddFriendsView.xib */; };
+ 294E496D1F8A6AA800045345 /* AddFriendsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294E49681F8A6AA800045345 /* AddFriendsCell.swift */; };
+ 2950182F1D9A5CE000AFD8B3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2950182E1D9A5CE000AFD8B3 /* GoogleService-Info.plist */; };
+ 295C70C21FE0486700109DF0 /* DialogflowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295C70C11FE0486700109DF0 /* DialogflowView.swift */; };
+ 295D93702094929A00A222CA /* creategroup_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 295D936F2094929A00A222CA /* creategroup_blank@2x.png */; };
+ 295D9376209492B000A222CA /* group_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 295D9372209492AF00A222CA /* group_blank@2x.png */; };
+ 295D9377209492B000A222CA /* group_more@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 295D9373209492AF00A222CA /* group_more@2x.png */; };
+ 295D9378209492B000A222CA /* groups_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 295D9375209492AF00A222CA /* groups_blank@2x.png */; };
+ 295D93842094930400A222CA /* GroupsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295D937A2094930400A222CA /* GroupsView.swift */; };
+ 295D93852094930400A222CA /* GroupsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295D937B2094930400A222CA /* GroupsCell.swift */; };
+ 295D93862094930400A222CA /* GroupsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 295D937C2094930400A222CA /* GroupsView.xib */; };
+ 295D93872094930400A222CA /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295D937E2094930400A222CA /* CreateGroupView.swift */; };
+ 295D93882094930400A222CA /* CreateGroupView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 295D937F2094930400A222CA /* CreateGroupView.xib */; };
+ 295D93892094930400A222CA /* GroupsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 295D93812094930400A222CA /* GroupsCell.xib */; };
+ 295D938C209496CF00A222CA /* DBGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295D938B209496CF00A222CA /* DBGroup.swift */; };
+ 295D938F2094A0D500A222CA /* Groups.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295D938D2094A0D500A222CA /* Groups.swift */; };
+ 295D93922094A44900A222CA /* Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295D93902094A44900A222CA /* Group.swift */; };
+ 295D93942094A65500A222CA /* tab_groups@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 295D93932094A65500A222CA /* tab_groups@2x.png */; };
+ 295D93992094AAF300A222CA /* GroupView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 295D93962094AAF200A222CA /* GroupView.xib */; };
+ 296021471F2CF5F600544468 /* ChatPrivateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29601FE51F2CF5F600544468 /* ChatPrivateView.swift */; };
+ 296021481F2CF5F600544468 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29601FE91F2CF5F600544468 /* ProfileView.swift */; };
+ 296021491F2CF5F600544468 /* ProfileView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29601FEA1F2CF5F600544468 /* ProfileView.xib */; };
+ 2960214C1F2CF5F600544468 /* AllMediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29601FF11F2CF5F600544468 /* AllMediaCell.swift */; };
+ 2960214D1F2CF5F600544468 /* AllMediaCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29601FF21F2CF5F600544468 /* AllMediaCell.xib */; };
+ 2960214E1F2CF5F600544468 /* AllMediaHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29601FF41F2CF5F600544468 /* AllMediaHeader.swift */; };
+ 2960214F1F2CF5F600544468 /* AllMediaHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29601FF51F2CF5F600544468 /* AllMediaHeader.xib */; };
+ 296021501F2CF5F600544468 /* AllMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29601FF71F2CF5F600544468 /* AllMediaView.swift */; };
+ 296021511F2CF5F600544468 /* AllMediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29601FF81F2CF5F600544468 /* AllMediaView.xib */; };
+ 296021531F2CF5F600544468 /* VideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29601FFE1F2CF5F600544468 /* VideoView.swift */; };
+ 296021541F2CF5F600544468 /* VideoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29601FFF1F2CF5F600544468 /* VideoView.xib */; };
+ 296021551F2CF5F600544468 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020021F2CF5F600544468 /* MapView.swift */; };
+ 296021561F2CF5F600544468 /* MapView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020031F2CF5F600544468 /* MapView.xib */; };
+ 296021591F2CF5F600544468 /* CallAudioView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960200B1F2CF5F600544468 /* CallAudioView.xib */; };
+ 2960215A1F2CF5F600544468 /* CallVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960200E1F2CF5F600544468 /* CallVideoView.swift */; };
+ 2960215B1F2CF5F600544468 /* CallVideoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960200F1F2CF5F600544468 /* CallVideoView.xib */; };
+ 2960215C1F2CF5F600544468 /* CountriesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020121F2CF5F600544468 /* CountriesView.swift */; };
+ 2960215D1F2CF5F600544468 /* CountriesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020131F2CF5F600544468 /* CountriesView.xib */; };
+ 296021621F2CF5F600544468 /* SelectUsersCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960201D1F2CF5F600544468 /* SelectUsersCell.swift */; };
+ 296021631F2CF5F600544468 /* SelectUsersCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960201E1F2CF5F600544468 /* SelectUsersCell.xib */; };
+ 296021641F2CF5F600544468 /* SelectUsersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020201F2CF5F600544468 /* SelectUsersView.swift */; };
+ 296021651F2CF5F600544468 /* SelectUsersView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020211F2CF5F600544468 /* SelectUsersView.xib */; };
+ 296021661F2CF5F600544468 /* SelectUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020241F2CF5F600544468 /* SelectUserCell.swift */; };
+ 296021671F2CF5F600544468 /* SelectUserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020251F2CF5F600544468 /* SelectUserCell.xib */; };
+ 296021681F2CF5F600544468 /* SelectUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020271F2CF5F600544468 /* SelectUserView.swift */; };
+ 296021691F2CF5F600544468 /* SelectUserView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020281F2CF5F600544468 /* SelectUserView.xib */; };
+ 296021701F2CF5F600544468 /* StickersCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020351F2CF5F600544468 /* StickersCell.swift */; };
+ 296021711F2CF5F600544468 /* StickersCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020361F2CF5F600544468 /* StickersCell.xib */; };
+ 296021721F2CF5F600544468 /* StickersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020381F2CF5F600544468 /* StickersView.swift */; };
+ 296021731F2CF5F600544468 /* StickersView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020391F2CF5F600544468 /* StickersView.xib */; };
+ 296021741F2CF5F600544468 /* ChatsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960203D1F2CF5F600544468 /* ChatsCell.swift */; };
+ 296021751F2CF5F600544468 /* ChatsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960203E1F2CF5F600544468 /* ChatsCell.xib */; };
+ 296021761F2CF5F600544468 /* ChatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020401F2CF5F600544468 /* ChatsView.swift */; };
+ 296021771F2CF5F600544468 /* ChatsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020411F2CF5F600544468 /* ChatsView.xib */; };
+ 296021781F2CF5F600544468 /* CallsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020441F2CF5F600544468 /* CallsView.swift */; };
+ 296021791F2CF5F600544468 /* CallsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020451F2CF5F600544468 /* CallsView.xib */; };
+ 2960217A1F2CF5F600544468 /* PeopleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020481F2CF5F600544468 /* PeopleCell.swift */; };
+ 2960217B1F2CF5F600544468 /* PeopleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020491F2CF5F600544468 /* PeopleCell.xib */; };
+ 2960217C1F2CF5F600544468 /* PeopleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960204B1F2CF5F600544468 /* PeopleView.swift */; };
+ 2960217D1F2CF5F600544468 /* PeopleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960204C1F2CF5F600544468 /* PeopleView.xib */; };
+ 296021841F2CF5F600544468 /* EditProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960205B1F2CF5F600544468 /* EditProfileView.swift */; };
+ 296021851F2CF5F600544468 /* EditProfileView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960205C1F2CF5F600544468 /* EditProfileView.xib */; };
+ 296021861F2CF5F600544468 /* PasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960205F1F2CF5F600544468 /* PasswordView.swift */; };
+ 296021871F2CF5F600544468 /* PasswordView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020601F2CF5F600544468 /* PasswordView.xib */; };
+ 296021881F2CF5F600544468 /* CustomStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020641F2CF5F600544468 /* CustomStatusView.swift */; };
+ 296021891F2CF5F600544468 /* CustomStatusView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020651F2CF5F600544468 /* CustomStatusView.xib */; };
+ 2960218A1F2CF5F600544468 /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020671F2CF5F600544468 /* StatusView.swift */; };
+ 2960218B1F2CF5F600544468 /* StatusView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020681F2CF5F600544468 /* StatusView.xib */; };
+ 2960218C1F2CF5F600544468 /* BlockedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960206B1F2CF5F600544468 /* BlockedCell.swift */; };
+ 2960218D1F2CF5F600544468 /* BlockedCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960206C1F2CF5F600544468 /* BlockedCell.xib */; };
+ 2960218E1F2CF5F600544468 /* BlockedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960206E1F2CF5F600544468 /* BlockedView.swift */; };
+ 2960218F1F2CF5F600544468 /* BlockedView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960206F1F2CF5F600544468 /* BlockedView.xib */; };
+ 296021901F2CF5F600544468 /* ArchiveCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020721F2CF5F600544468 /* ArchiveCell.swift */; };
+ 296021911F2CF5F600544468 /* ArchiveCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020731F2CF5F600544468 /* ArchiveCell.xib */; };
+ 296021921F2CF5F600544468 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020751F2CF5F600544468 /* ArchiveView.swift */; };
+ 296021931F2CF5F600544468 /* ArchiveView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020761F2CF5F600544468 /* ArchiveView.xib */; };
+ 296021941F2CF5F600544468 /* KeepMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960207A1F2CF5F600544468 /* KeepMediaView.swift */; };
+ 296021951F2CF5F600544468 /* KeepMediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960207B1F2CF5F600544468 /* KeepMediaView.xib */; };
+ 296021961F2CF5F600544468 /* CacheView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960207D1F2CF5F600544468 /* CacheView.swift */; };
+ 296021971F2CF5F600544468 /* CacheView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960207E1F2CF5F600544468 /* CacheView.xib */; };
+ 296021981F2CF5F600544468 /* NetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020821F2CF5F600544468 /* NetworkView.swift */; };
+ 296021991F2CF5F600544468 /* NetworkView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020831F2CF5F600544468 /* NetworkView.xib */; };
+ 2960219A1F2CF5F600544468 /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020851F2CF5F600544468 /* MediaView.swift */; };
+ 2960219B1F2CF5F600544468 /* MediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020861F2CF5F600544468 /* MediaView.xib */; };
+ 2960219C1F2CF5F600544468 /* WallpapersCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020891F2CF5F600544468 /* WallpapersCell.swift */; };
+ 2960219D1F2CF5F600544468 /* WallpapersCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960208A1F2CF5F600544468 /* WallpapersCell.xib */; };
+ 2960219E1F2CF5F600544468 /* WallpapersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960208C1F2CF5F600544468 /* WallpapersView.swift */; };
+ 2960219F1F2CF5F600544468 /* WallpapersView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960208D1F2CF5F600544468 /* WallpapersView.xib */; };
+ 296021A01F2CF5F600544468 /* PrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020901F2CF5F600544468 /* PrivacyView.swift */; };
+ 296021A11F2CF5F600544468 /* PrivacyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020911F2CF5F600544468 /* PrivacyView.xib */; };
+ 296021A21F2CF5F600544468 /* TermsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020941F2CF5F600544468 /* TermsView.swift */; };
+ 296021A31F2CF5F600544468 /* TermsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020951F2CF5F600544468 /* TermsView.xib */; };
+ 296021A41F2CF5F600544468 /* AddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020981F2CF5F600544468 /* AddAccountView.swift */; };
+ 296021A51F2CF5F600544468 /* AddAccountView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020991F2CF5F600544468 /* AddAccountView.xib */; };
+ 296021A61F2CF5F600544468 /* SwitchAccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960209C1F2CF5F600544468 /* SwitchAccountCell.swift */; };
+ 296021A71F2CF5F600544468 /* SwitchAccountCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960209D1F2CF5F600544468 /* SwitchAccountCell.xib */; };
+ 296021A81F2CF5F600544468 /* SwitchAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960209F1F2CF5F600544468 /* SwitchAccountView.swift */; };
+ 296021A91F2CF5F600544468 /* SwitchAccountView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020A01F2CF5F600544468 /* SwitchAccountView.xib */; };
+ 296021AA1F2CF5F600544468 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020A21F2CF5F600544468 /* SettingsView.swift */; };
+ 296021AB1F2CF5F600544468 /* SettingsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020A31F2CF5F600544468 /* SettingsView.xib */; };
+ 296021AC1F2CF5F600544468 /* advert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020A71F2CF5F600544468 /* advert.swift */; };
+ 296021AD1F2CF5F600544468 /* AdvertCustomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020A91F2CF5F600544468 /* AdvertCustomView.swift */; };
+ 296021AE1F2CF5F600544468 /* AdvertCustomView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020AA1F2CF5F600544468 /* AdvertCustomView.xib */; };
+ 296021AF1F2CF5F600544468 /* AdvertPremiumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020AC1F2CF5F600544468 /* AdvertPremiumView.swift */; };
+ 296021B01F2CF5F600544468 /* AdvertPremiumView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296020AD1F2CF5F600544468 /* AdvertPremiumView.xib */; };
+ 296021B41F2CF5F600544468 /* NSError+Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020B61F2CF5F600544468 /* NSError+Util.swift */; };
+ 296021B51F2CF5F600544468 /* Blockeds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020B91F2CF5F600544468 /* Blockeds.swift */; };
+ 296021B61F2CF5F600544468 /* CallHistories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020BB1F2CF5F600544468 /* CallHistories.swift */; };
+ 296021B91F2CF5F600544468 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020C11F2CF5F600544468 /* Messages.swift */; };
+ 296021BA1F2CF5F600544468 /* Statuses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020C31F2CF5F600544468 /* Statuses.swift */; };
+ 296021BB1F2CF5F600544468 /* Users.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020C51F2CF5F600544468 /* Users.swift */; };
+ 296021BC1F2CF5F600544468 /* UserStatuses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020C71F2CF5F600544468 /* UserStatuses.swift */; };
+ 296021BD1F2CF5F600544468 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020CA1F2CF5F600544468 /* Account.swift */; };
+ 296021BE1F2CF5F600544468 /* Blocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020CC1F2CF5F600544468 /* Blocked.swift */; };
+ 296021BF1F2CF5F600544468 /* CallHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020CE1F2CF5F600544468 /* CallHistory.swift */; };
+ 296021C01F2CF5F600544468 /* Chat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020D01F2CF5F600544468 /* Chat.swift */; };
+ 296021C21F2CF5F600544468 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020D41F2CF5F600544468 /* Message.swift */; };
+ 296021C31F2CF5F600544468 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020D61F2CF5F600544468 /* Status.swift */; };
+ 296021C41F2CF5F600544468 /* push.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020D91F2CF5F600544468 /* push.swift */; };
+ 296021C51F2CF5F600544468 /* user.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020DB1F2CF5F600544468 /* user.swift */; };
+ 296021C61F2CF5F600544468 /* NotificationCenterX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020DE1F2CF5F600544468 /* NotificationCenterX.swift */; };
+ 296021C91F2CF5F600544468 /* UserDefaultsX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020E41F2CF5F600544468 /* UserDefaultsX.swift */; };
+ 296021CA1F2CF5F600544468 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020E71F2CF5F600544468 /* Connection.swift */; };
+ 296021CB1F2CF5F600544468 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020E91F2CF5F600544468 /* Location.swift */; };
+ 296021CC1F2CF5F600544468 /* Audio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020EC1F2CF5F600544468 /* Audio.swift */; };
+ 296021CD1F2CF5F600544468 /* Checksum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020EE1F2CF5F600544468 /* Checksum.swift */; };
+ 296021CE1F2CF5F600544468 /* Cryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020F01F2CF5F600544468 /* Cryptor.swift */; };
+ 296021CF1F2CF5F600544468 /* Dir.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020F21F2CF5F600544468 /* Dir.swift */; };
+ 296021D01F2CF5F600544468 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020F41F2CF5F600544468 /* Emoji.swift */; };
+ 296021D11F2CF5F600544468 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020F61F2CF5F600544468 /* File.swift */; };
+ 296021D21F2CF5F600544468 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020F81F2CF5F600544468 /* Image.swift */; };
+ 296021D31F2CF5F600544468 /* Password.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020FA1F2CF5F600544468 /* Password.swift */; };
+ 296021D41F2CF5F600544468 /* Shortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020FC1F2CF5F600544468 /* Shortcut.swift */; };
+ 296021D51F2CF5F600544468 /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296020FE1F2CF5F600544468 /* Video.swift */; };
+ 296021D61F2CF5F600544468 /* camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021011F2CF5F600544468 /* camera.swift */; };
+ 296021D81F2CF5F600544468 /* converter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021051F2CF5F600544468 /* converter.swift */; };
+ 296021D91F2CF5F600544468 /* CacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021081F2CF5F600544468 /* CacheManager.swift */; };
+ 296021DA1F2CF5F600544468 /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960210A1F2CF5F600544468 /* DownloadManager.swift */; };
+ 296021DB1F2CF5F600544468 /* MediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960210C1F2CF5F600544468 /* MediaLoader.swift */; };
+ 296021DC1F2CF5F600544468 /* RealmManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960210E1F2CF5F600544468 /* RealmManager.swift */; };
+ 296021DE1F2CF5F600544468 /* MessageQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021131F2CF5F600544468 /* MessageQueue.swift */; };
+ 296021E01F2CF5F600544468 /* MessageRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021171F2CF5F600544468 /* MessageRelay.swift */; };
+ 296021E11F2CF5F600544468 /* NYTPhotoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960211A1F2CF5F600544468 /* NYTPhotoItem.swift */; };
+ 296021E21F2CF5F600544468 /* DBBlocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960211D1F2CF5F600544468 /* DBBlocked.swift */; };
+ 296021E31F2CF5F600544468 /* DBCallHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960211F1F2CF5F600544468 /* DBCallHistory.swift */; };
+ 296021E41F2CF5F600544468 /* DBChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021211F2CF5F600544468 /* DBChat.swift */; };
+ 296021E71F2CF5F600544468 /* DBMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021271F2CF5F600544468 /* DBMessage.swift */; };
+ 296021E81F2CF5F600544468 /* DBStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021291F2CF5F600544468 /* DBStatus.swift */; };
+ 296021E91F2CF5F600544468 /* DBUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960212B1F2CF5F600544468 /* DBUser.swift */; };
+ 296021EA1F2CF5F600544468 /* DBUserStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960212D1F2CF5F600544468 /* DBUserStatus.swift */; };
+ 296021EB1F2CF5F600544468 /* LoginEmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021321F2CF5F600544468 /* LoginEmailView.swift */; };
+ 296021EC1F2CF5F600544468 /* LoginEmailView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296021331F2CF5F600544468 /* LoginEmailView.xib */; };
+ 296021ED1F2CF5F600544468 /* LoginGoogleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021361F2CF5F600544468 /* LoginGoogleView.swift */; };
+ 296021EE1F2CF5F600544468 /* LoginGoogleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296021371F2CF5F600544468 /* LoginGoogleView.xib */; };
+ 296021EF1F2CF5F600544468 /* VerifySMSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960213B1F2CF5F600544468 /* VerifySMSView.swift */; };
+ 296021F01F2CF5F600544468 /* VerifySMSView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960213C1F2CF5F600544468 /* VerifySMSView.xib */; };
+ 296021F11F2CF5F600544468 /* LoginPhoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960213E1F2CF5F600544468 /* LoginPhoneView.swift */; };
+ 296021F21F2CF5F600544468 /* LoginPhoneView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2960213F1F2CF5F600544468 /* LoginPhoneView.xib */; };
+ 296021F31F2CF5F600544468 /* RegisterEmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021421F2CF5F600544468 /* RegisterEmailView.swift */; };
+ 296021F41F2CF5F600544468 /* RegisterEmailView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296021431F2CF5F600544468 /* RegisterEmailView.xib */; };
+ 296021F51F2CF5F600544468 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296021451F2CF5F600544468 /* WelcomeView.swift */; };
+ 296021F61F2CF5F600544468 /* WelcomeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 296021461F2CF5F600544468 /* WelcomeView.xib */; };
+ 2974641D1FDF01100038D976 /* chats_dialogflow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2974641C1FDF01100038D976 /* chats_dialogflow@2x.png */; };
+ 2974C6FC21EA91AB00E173F1 /* SINSLazyMessageClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 2974C6CB21EA91AB00E173F1 /* SINSLazyMessageClient.m */; };
+ 2974C6FD21EA91AB00E173F1 /* SINService.m in Sources */ = {isa = PBXBuildFile; fileRef = 2974C6CC21EA91AB00E173F1 /* SINService.m */; settings = {COMPILER_FLAGS = "-w"; }; };
+ 2974C6FE21EA91AB00E173F1 /* SINSClientsObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 2974C6CD21EA91AB00E173F1 /* SINSClientsObserver.m */; };
+ 2974C6FF21EA91AB00E173F1 /* SINSLazyProxyBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 2974C6CE21EA91AB00E173F1 /* SINSLazyProxyBase.m */; };
+ 2974C70021EA91AB00E173F1 /* SINServiceError.m in Sources */ = {isa = PBXBuildFile; fileRef = 2974C6F021EA91AB00E173F1 /* SINServiceError.m */; };
+ 2974C70121EA91AB00E173F1 /* SINSServicePersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 2974C6F321EA91AB00E173F1 /* SINSServicePersistence.m */; };
+ 2974C70221EA91AB00E173F1 /* SinchService.m in Sources */ = {isa = PBXBuildFile; fileRef = 2974C6F721EA91AB00E173F1 /* SinchService.m */; };
+ 2974C70321EA91AB00E173F1 /* SINSLazyCallClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 2974C6F821EA91AB00E173F1 /* SINSLazyCallClient.m */; settings = {COMPILER_FLAGS = "-w"; }; };
+ 2974C70421EA91AB00E173F1 /* SINSLazyAudioController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2974C6FA21EA91AB00E173F1 /* SINSLazyAudioController.m */; settings = {COMPILER_FLAGS = "-w"; }; };
+ 297D027220ECA317000E6DB7 /* Date+Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297D027020ECA317000E6DB7 /* Date+Util.swift */; };
+ 29898FC81F1B6FFD00AC73B3 /* countries.plist in Resources */ = {isa = PBXBuildFile; fileRef = 29898ED21F1B6FFC00AC73B3 /* countries.plist */; };
+ 29898FC91F1B6FFD00AC73B3 /* call_incoming.wav in Resources */ = {isa = PBXBuildFile; fileRef = 29898ED51F1B6FFC00AC73B3 /* call_incoming.wav */; };
+ 29898FCA1F1B6FFD00AC73B3 /* call_ringback.wav in Resources */ = {isa = PBXBuildFile; fileRef = 29898ED61F1B6FFC00AC73B3 /* call_ringback.wav */; };
+ 29898FCB1F1B6FFD00AC73B3 /* stickerlocal01@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898ED81F1B6FFC00AC73B3 /* stickerlocal01@2x.png */; };
+ 29898FCC1F1B6FFD00AC73B3 /* stickerlocal02@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898ED91F1B6FFC00AC73B3 /* stickerlocal02@2x.png */; };
+ 29898FCD1F1B6FFD00AC73B3 /* stickerlocal03@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EDA1F1B6FFC00AC73B3 /* stickerlocal03@2x.png */; };
+ 29898FCE1F1B6FFD00AC73B3 /* stickerlocal04@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EDB1F1B6FFC00AC73B3 /* stickerlocal04@2x.png */; };
+ 29898FCF1F1B6FFD00AC73B3 /* stickerlocal05@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EDC1F1B6FFC00AC73B3 /* stickerlocal05@2x.png */; };
+ 29898FD01F1B6FFD00AC73B3 /* stickerlocal06@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EDD1F1B6FFC00AC73B3 /* stickerlocal06@2x.png */; };
+ 29898FD11F1B6FFD00AC73B3 /* stickerlocal07@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EDE1F1B6FFC00AC73B3 /* stickerlocal07@2x.png */; };
+ 29898FD21F1B6FFD00AC73B3 /* stickerlocal08@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EDF1F1B6FFC00AC73B3 /* stickerlocal08@2x.png */; };
+ 29898FD31F1B6FFD00AC73B3 /* stickerlocal09@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EE01F1B6FFC00AC73B3 /* stickerlocal09@2x.png */; };
+ 29898FD41F1B6FFD00AC73B3 /* stickerlocal10@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EE11F1B6FFC00AC73B3 /* stickerlocal10@2x.png */; };
+ 29898FD51F1B6FFD00AC73B3 /* stickerlocal11@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EE21F1B6FFC00AC73B3 /* stickerlocal11@2x.png */; };
+ 29898FD61F1B6FFD00AC73B3 /* stickerlocal12@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EE31F1B6FFC00AC73B3 /* stickerlocal12@2x.png */; };
+ 29898FD71F1B6FFD00AC73B3 /* stickerlocal13@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EE41F1B6FFC00AC73B3 /* stickerlocal13@2x.png */; };
+ 29898FD81F1B6FFD00AC73B3 /* stickerlocal14@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EE51F1B6FFC00AC73B3 /* stickerlocal14@2x.png */; };
+ 29898FD91F1B6FFD00AC73B3 /* stickerlocal15@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EE61F1B6FFC00AC73B3 /* stickerlocal15@2x.png */; };
+ 29898FDA1F1B6FFD00AC73B3 /* stickerlocal16@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EE71F1B6FFC00AC73B3 /* stickerlocal16@2x.png */; };
+ 29898FDB1F1B6FFD00AC73B3 /* stickerlocal17@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EE81F1B6FFC00AC73B3 /* stickerlocal17@2x.png */; };
+ 29898FDC1F1B6FFD00AC73B3 /* stickerlocal18@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EE91F1B6FFC00AC73B3 /* stickerlocal18@2x.png */; };
+ 29898FDD1F1B6FFD00AC73B3 /* stickerlocal19@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EEA1F1B6FFC00AC73B3 /* stickerlocal19@2x.png */; };
+ 29898FDE1F1B6FFD00AC73B3 /* stickerlocal20@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EEB1F1B6FFC00AC73B3 /* stickerlocal20@2x.png */; };
+ 29898FDF1F1B6FFD00AC73B3 /* stickerlocal21@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EEC1F1B6FFC00AC73B3 /* stickerlocal21@2x.png */; };
+ 29898FE01F1B6FFD00AC73B3 /* stickerlocal22@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EED1F1B6FFC00AC73B3 /* stickerlocal22@2x.png */; };
+ 29898FE11F1B6FFD00AC73B3 /* stickerlocal23@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EEE1F1B6FFC00AC73B3 /* stickerlocal23@2x.png */; };
+ 29898FE21F1B6FFD00AC73B3 /* stickerlocal24@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EEF1F1B6FFC00AC73B3 /* stickerlocal24@2x.png */; };
+ 29898FE31F1B6FFD00AC73B3 /* stickerlocal25@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EF01F1B6FFC00AC73B3 /* stickerlocal25@2x.png */; };
+ 29898FE41F1B6FFD00AC73B3 /* stickerlocal26@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EF11F1B6FFC00AC73B3 /* stickerlocal26@2x.png */; };
+ 29898FE51F1B6FFD00AC73B3 /* stickerlocal27@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EF21F1B6FFC00AC73B3 /* stickerlocal27@2x.png */; };
+ 29898FE61F1B6FFD00AC73B3 /* stickerlocal28@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EF31F1B6FFC00AC73B3 /* stickerlocal28@2x.png */; };
+ 29898FE71F1B6FFD00AC73B3 /* stickerlocal29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EF41F1B6FFC00AC73B3 /* stickerlocal29@2x.png */; };
+ 29898FE81F1B6FFD00AC73B3 /* stickerlocal30@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EF51F1B6FFC00AC73B3 /* stickerlocal30@2x.png */; };
+ 29898FE91F1B6FFD00AC73B3 /* stickerlocal31@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EF61F1B6FFC00AC73B3 /* stickerlocal31@2x.png */; };
+ 29898FEA1F1B6FFD00AC73B3 /* stickerlocal32@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EF71F1B6FFC00AC73B3 /* stickerlocal32@2x.png */; };
+ 29898FEB1F1B6FFD00AC73B3 /* stickerlocal33@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EF81F1B6FFC00AC73B3 /* stickerlocal33@2x.png */; };
+ 29898FEC1F1B6FFD00AC73B3 /* stickerlocal34@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EF91F1B6FFC00AC73B3 /* stickerlocal34@2x.png */; };
+ 29898FED1F1B6FFD00AC73B3 /* stickerlocal35@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EFA1F1B6FFC00AC73B3 /* stickerlocal35@2x.png */; };
+ 29898FEE1F1B6FFD00AC73B3 /* stickerlocal36@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EFB1F1B6FFC00AC73B3 /* stickerlocal36@2x.png */; };
+ 29898FEF1F1B6FFD00AC73B3 /* stickerlocal37@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EFC1F1B6FFC00AC73B3 /* stickerlocal37@2x.png */; };
+ 29898FF01F1B6FFD00AC73B3 /* stickerlocal38@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EFD1F1B6FFC00AC73B3 /* stickerlocal38@2x.png */; };
+ 29898FF11F1B6FFD00AC73B3 /* stickerlocal39@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EFE1F1B6FFC00AC73B3 /* stickerlocal39@2x.png */; };
+ 29898FF21F1B6FFD00AC73B3 /* stickerlocal40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898EFF1F1B6FFC00AC73B3 /* stickerlocal40@2x.png */; };
+ 29898FF31F1B6FFD00AC73B3 /* stickerlocal41@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F001F1B6FFC00AC73B3 /* stickerlocal41@2x.png */; };
+ 29898FF41F1B6FFD00AC73B3 /* stickerlocal42@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F011F1B6FFC00AC73B3 /* stickerlocal42@2x.png */; };
+ 29898FF51F1B6FFD00AC73B3 /* stickerlocal43@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F021F1B6FFC00AC73B3 /* stickerlocal43@2x.png */; };
+ 29898FF61F1B6FFD00AC73B3 /* stickerlocal44@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F031F1B6FFC00AC73B3 /* stickerlocal44@2x.png */; };
+ 29898FF71F1B6FFD00AC73B3 /* stickerlocal45@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F041F1B6FFC00AC73B3 /* stickerlocal45@2x.png */; };
+ 29898FF81F1B6FFD00AC73B3 /* stickerlocal46@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F051F1B6FFC00AC73B3 /* stickerlocal46@2x.png */; };
+ 29898FF91F1B6FFD00AC73B3 /* stickerlocal47@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F061F1B6FFC00AC73B3 /* stickerlocal47@2x.png */; };
+ 29898FFA1F1B6FFD00AC73B3 /* stickerlocal48@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F071F1B6FFC00AC73B3 /* stickerlocal48@2x.png */; };
+ 29898FFB1F1B6FFD00AC73B3 /* stickerlocal49@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F081F1B6FFC00AC73B3 /* stickerlocal49@2x.png */; };
+ 29898FFC1F1B6FFD00AC73B3 /* stickerlocal50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F091F1B6FFC00AC73B3 /* stickerlocal50@2x.png */; };
+ 29898FFD1F1B6FFD00AC73B3 /* stickerlocal51@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F0A1F1B6FFC00AC73B3 /* stickerlocal51@2x.png */; };
+ 29898FFE1F1B6FFD00AC73B3 /* stickerlocal52@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F0B1F1B6FFC00AC73B3 /* stickerlocal52@2x.png */; };
+ 29898FFF1F1B6FFD00AC73B3 /* stickerlocal53@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F0C1F1B6FFC00AC73B3 /* stickerlocal53@2x.png */; };
+ 298990001F1B6FFD00AC73B3 /* stickerlocal54@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F0D1F1B6FFC00AC73B3 /* stickerlocal54@2x.png */; };
+ 298990011F1B6FFD00AC73B3 /* stickerlocal55@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F0E1F1B6FFC00AC73B3 /* stickerlocal55@2x.png */; };
+ 298990021F1B6FFD00AC73B3 /* stickerlocal56@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F0F1F1B6FFC00AC73B3 /* stickerlocal56@2x.png */; };
+ 298990031F1B6FFD00AC73B3 /* stickerlocal57@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F101F1B6FFC00AC73B3 /* stickerlocal57@2x.png */; };
+ 298990041F1B6FFD00AC73B3 /* stickerlocal58@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F111F1B6FFC00AC73B3 /* stickerlocal58@2x.png */; };
+ 298990051F1B6FFD00AC73B3 /* stickerlocal59@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F121F1B6FFC00AC73B3 /* stickerlocal59@2x.png */; };
+ 298990061F1B6FFD00AC73B3 /* stickerlocal60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F131F1B6FFC00AC73B3 /* stickerlocal60@2x.png */; };
+ 298990071F1B6FFD00AC73B3 /* stickerlocal61@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F141F1B6FFC00AC73B3 /* stickerlocal61@2x.png */; };
+ 298990081F1B6FFD00AC73B3 /* stickerlocal62@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F151F1B6FFC00AC73B3 /* stickerlocal62@2x.png */; };
+ 298990091F1B6FFD00AC73B3 /* stickerlocal63@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F161F1B6FFC00AC73B3 /* stickerlocal63@2x.png */; };
+ 2989900A1F1B6FFD00AC73B3 /* stickerlocal64@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F171F1B6FFC00AC73B3 /* stickerlocal64@2x.png */; };
+ 2989900B1F1B6FFD00AC73B3 /* stickerlocal65@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F181F1B6FFC00AC73B3 /* stickerlocal65@2x.png */; };
+ 2989900C1F1B6FFD00AC73B3 /* stickerlocal66@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F191F1B6FFC00AC73B3 /* stickerlocal66@2x.png */; };
+ 2989900D1F1B6FFD00AC73B3 /* stickerlocal67@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F1A1F1B6FFC00AC73B3 /* stickerlocal67@2x.png */; };
+ 2989900E1F1B6FFD00AC73B3 /* stickerlocal68@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F1B1F1B6FFC00AC73B3 /* stickerlocal68@2x.png */; };
+ 2989900F1F1B6FFD00AC73B3 /* stickerlocal69@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F1C1F1B6FFC00AC73B3 /* stickerlocal69@2x.png */; };
+ 298990101F1B6FFD00AC73B3 /* stickerlocal70@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F1D1F1B6FFC00AC73B3 /* stickerlocal70@2x.png */; };
+ 298990111F1B6FFD00AC73B3 /* stickerlocal71@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F1E1F1B6FFC00AC73B3 /* stickerlocal71@2x.png */; };
+ 298990121F1B6FFD00AC73B3 /* stickerlocal72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F1F1F1B6FFC00AC73B3 /* stickerlocal72@2x.png */; };
+ 298990131F1B6FFD00AC73B3 /* stickerlocal73@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F201F1B6FFC00AC73B3 /* stickerlocal73@2x.png */; };
+ 298990141F1B6FFD00AC73B3 /* stickerlocal74@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F211F1B6FFC00AC73B3 /* stickerlocal74@2x.png */; };
+ 298990151F1B6FFD00AC73B3 /* stickerlocal75@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F221F1B6FFC00AC73B3 /* stickerlocal75@2x.png */; };
+ 298990161F1B6FFD00AC73B3 /* stickerlocal76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F231F1B6FFC00AC73B3 /* stickerlocal76@2x.png */; };
+ 298990171F1B6FFD00AC73B3 /* stickerlocal77@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F241F1B6FFC00AC73B3 /* stickerlocal77@2x.png */; };
+ 298990181F1B6FFD00AC73B3 /* stickerlocal78@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F251F1B6FFC00AC73B3 /* stickerlocal78@2x.png */; };
+ 298990191F1B6FFD00AC73B3 /* stickersend01@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F271F1B6FFC00AC73B3 /* stickersend01@2x.png */; };
+ 2989901A1F1B6FFD00AC73B3 /* stickersend02@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F281F1B6FFC00AC73B3 /* stickersend02@2x.png */; };
+ 2989901B1F1B6FFD00AC73B3 /* stickersend03@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F291F1B6FFC00AC73B3 /* stickersend03@2x.png */; };
+ 2989901C1F1B6FFD00AC73B3 /* stickersend04@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F2A1F1B6FFC00AC73B3 /* stickersend04@2x.png */; };
+ 2989901D1F1B6FFD00AC73B3 /* stickersend05@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F2B1F1B6FFC00AC73B3 /* stickersend05@2x.png */; };
+ 2989901E1F1B6FFD00AC73B3 /* stickersend06@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F2C1F1B6FFC00AC73B3 /* stickersend06@2x.png */; };
+ 2989901F1F1B6FFD00AC73B3 /* stickersend07@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F2D1F1B6FFC00AC73B3 /* stickersend07@2x.png */; };
+ 298990201F1B6FFD00AC73B3 /* stickersend08@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F2E1F1B6FFC00AC73B3 /* stickersend08@2x.png */; };
+ 298990211F1B6FFD00AC73B3 /* stickersend09@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F2F1F1B6FFC00AC73B3 /* stickersend09@2x.png */; };
+ 298990221F1B6FFD00AC73B3 /* stickersend10@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F301F1B6FFC00AC73B3 /* stickersend10@2x.png */; };
+ 298990231F1B6FFD00AC73B3 /* stickersend11@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F311F1B6FFC00AC73B3 /* stickersend11@2x.png */; };
+ 298990241F1B6FFD00AC73B3 /* stickersend12@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F321F1B6FFC00AC73B3 /* stickersend12@2x.png */; };
+ 298990251F1B6FFD00AC73B3 /* stickersend13@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F331F1B6FFC00AC73B3 /* stickersend13@2x.png */; };
+ 298990261F1B6FFD00AC73B3 /* stickersend14@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F341F1B6FFC00AC73B3 /* stickersend14@2x.png */; };
+ 298990271F1B6FFD00AC73B3 /* stickersend15@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F351F1B6FFC00AC73B3 /* stickersend15@2x.png */; };
+ 298990281F1B6FFD00AC73B3 /* stickersend16@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F361F1B6FFC00AC73B3 /* stickersend16@2x.png */; };
+ 298990291F1B6FFD00AC73B3 /* stickersend17@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F371F1B6FFC00AC73B3 /* stickersend17@2x.png */; };
+ 2989902A1F1B6FFD00AC73B3 /* stickersend18@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F381F1B6FFC00AC73B3 /* stickersend18@2x.png */; };
+ 2989902B1F1B6FFD00AC73B3 /* stickersend19@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F391F1B6FFC00AC73B3 /* stickersend19@2x.png */; };
+ 2989902C1F1B6FFD00AC73B3 /* stickersend20@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F3A1F1B6FFC00AC73B3 /* stickersend20@2x.png */; };
+ 2989902D1F1B6FFD00AC73B3 /* stickersend21@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F3B1F1B6FFC00AC73B3 /* stickersend21@2x.png */; };
+ 2989902E1F1B6FFD00AC73B3 /* stickersend22@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F3C1F1B6FFC00AC73B3 /* stickersend22@2x.png */; };
+ 2989902F1F1B6FFD00AC73B3 /* stickersend23@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F3D1F1B6FFC00AC73B3 /* stickersend23@2x.png */; };
+ 298990301F1B6FFD00AC73B3 /* stickersend24@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F3E1F1B6FFC00AC73B3 /* stickersend24@2x.png */; };
+ 298990311F1B6FFD00AC73B3 /* stickersend25@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F3F1F1B6FFC00AC73B3 /* stickersend25@2x.png */; };
+ 298990321F1B6FFD00AC73B3 /* stickersend26@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F401F1B6FFC00AC73B3 /* stickersend26@2x.png */; };
+ 298990331F1B6FFD00AC73B3 /* stickersend27@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F411F1B6FFC00AC73B3 /* stickersend27@2x.png */; };
+ 298990341F1B6FFD00AC73B3 /* stickersend28@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F421F1B6FFC00AC73B3 /* stickersend28@2x.png */; };
+ 298990351F1B6FFD00AC73B3 /* stickersend29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F431F1B6FFC00AC73B3 /* stickersend29@2x.png */; };
+ 298990361F1B6FFD00AC73B3 /* stickersend30@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F441F1B6FFC00AC73B3 /* stickersend30@2x.png */; };
+ 298990371F1B6FFD00AC73B3 /* stickersend31@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F451F1B6FFC00AC73B3 /* stickersend31@2x.png */; };
+ 298990381F1B6FFD00AC73B3 /* stickersend32@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F461F1B6FFC00AC73B3 /* stickersend32@2x.png */; };
+ 298990391F1B6FFD00AC73B3 /* stickersend33@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F471F1B6FFC00AC73B3 /* stickersend33@2x.png */; };
+ 2989903A1F1B6FFD00AC73B3 /* stickersend34@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F481F1B6FFC00AC73B3 /* stickersend34@2x.png */; };
+ 2989903B1F1B6FFD00AC73B3 /* stickersend35@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F491F1B6FFC00AC73B3 /* stickersend35@2x.png */; };
+ 2989903C1F1B6FFD00AC73B3 /* stickersend36@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F4A1F1B6FFC00AC73B3 /* stickersend36@2x.png */; };
+ 2989903D1F1B6FFD00AC73B3 /* stickersend37@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F4B1F1B6FFC00AC73B3 /* stickersend37@2x.png */; };
+ 2989903E1F1B6FFD00AC73B3 /* stickersend38@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F4C1F1B6FFC00AC73B3 /* stickersend38@2x.png */; };
+ 2989903F1F1B6FFD00AC73B3 /* stickersend39@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F4D1F1B6FFC00AC73B3 /* stickersend39@2x.png */; };
+ 298990401F1B6FFD00AC73B3 /* stickersend40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F4E1F1B6FFC00AC73B3 /* stickersend40@2x.png */; };
+ 298990411F1B6FFD00AC73B3 /* stickersend41@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F4F1F1B6FFC00AC73B3 /* stickersend41@2x.png */; };
+ 298990421F1B6FFD00AC73B3 /* stickersend42@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F501F1B6FFC00AC73B3 /* stickersend42@2x.png */; };
+ 298990431F1B6FFD00AC73B3 /* stickersend43@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F511F1B6FFC00AC73B3 /* stickersend43@2x.png */; };
+ 298990441F1B6FFD00AC73B3 /* stickersend44@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F521F1B6FFC00AC73B3 /* stickersend44@2x.png */; };
+ 298990451F1B6FFD00AC73B3 /* stickersend45@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F531F1B6FFC00AC73B3 /* stickersend45@2x.png */; };
+ 298990461F1B6FFD00AC73B3 /* stickersend46@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F541F1B6FFC00AC73B3 /* stickersend46@2x.png */; };
+ 298990471F1B6FFD00AC73B3 /* stickersend47@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F551F1B6FFC00AC73B3 /* stickersend47@2x.png */; };
+ 298990481F1B6FFD00AC73B3 /* stickersend48@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F561F1B6FFC00AC73B3 /* stickersend48@2x.png */; };
+ 298990491F1B6FFD00AC73B3 /* stickersend49@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F571F1B6FFC00AC73B3 /* stickersend49@2x.png */; };
+ 2989904A1F1B6FFD00AC73B3 /* stickersend50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F581F1B6FFC00AC73B3 /* stickersend50@2x.png */; };
+ 2989904B1F1B6FFD00AC73B3 /* stickersend51@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F591F1B6FFC00AC73B3 /* stickersend51@2x.png */; };
+ 2989904C1F1B6FFD00AC73B3 /* stickersend52@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F5A1F1B6FFC00AC73B3 /* stickersend52@2x.png */; };
+ 2989904D1F1B6FFD00AC73B3 /* stickersend53@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F5B1F1B6FFC00AC73B3 /* stickersend53@2x.png */; };
+ 2989904E1F1B6FFD00AC73B3 /* stickersend54@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F5C1F1B6FFC00AC73B3 /* stickersend54@2x.png */; };
+ 2989904F1F1B6FFD00AC73B3 /* stickersend55@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F5D1F1B6FFC00AC73B3 /* stickersend55@2x.png */; };
+ 298990501F1B6FFD00AC73B3 /* stickersend56@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F5E1F1B6FFD00AC73B3 /* stickersend56@2x.png */; };
+ 298990511F1B6FFD00AC73B3 /* stickersend57@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F5F1F1B6FFD00AC73B3 /* stickersend57@2x.png */; };
+ 298990521F1B6FFD00AC73B3 /* stickersend58@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F601F1B6FFD00AC73B3 /* stickersend58@2x.png */; };
+ 298990531F1B6FFD00AC73B3 /* stickersend59@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F611F1B6FFD00AC73B3 /* stickersend59@2x.png */; };
+ 298990541F1B6FFD00AC73B3 /* stickersend60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F621F1B6FFD00AC73B3 /* stickersend60@2x.png */; };
+ 298990551F1B6FFD00AC73B3 /* stickersend61@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F631F1B6FFD00AC73B3 /* stickersend61@2x.png */; };
+ 298990561F1B6FFD00AC73B3 /* stickersend62@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F641F1B6FFD00AC73B3 /* stickersend62@2x.png */; };
+ 298990571F1B6FFD00AC73B3 /* stickersend63@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F651F1B6FFD00AC73B3 /* stickersend63@2x.png */; };
+ 298990581F1B6FFD00AC73B3 /* stickersend64@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F661F1B6FFD00AC73B3 /* stickersend64@2x.png */; };
+ 298990591F1B6FFD00AC73B3 /* stickersend65@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F671F1B6FFD00AC73B3 /* stickersend65@2x.png */; };
+ 2989905A1F1B6FFD00AC73B3 /* stickersend66@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F681F1B6FFD00AC73B3 /* stickersend66@2x.png */; };
+ 2989905B1F1B6FFD00AC73B3 /* stickersend67@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F691F1B6FFD00AC73B3 /* stickersend67@2x.png */; };
+ 2989905C1F1B6FFD00AC73B3 /* stickersend68@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F6A1F1B6FFD00AC73B3 /* stickersend68@2x.png */; };
+ 2989905D1F1B6FFD00AC73B3 /* stickersend69@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F6B1F1B6FFD00AC73B3 /* stickersend69@2x.png */; };
+ 2989905E1F1B6FFD00AC73B3 /* stickersend70@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F6C1F1B6FFD00AC73B3 /* stickersend70@2x.png */; };
+ 2989905F1F1B6FFD00AC73B3 /* stickersend71@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F6D1F1B6FFD00AC73B3 /* stickersend71@2x.png */; };
+ 298990601F1B6FFD00AC73B3 /* stickersend72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F6E1F1B6FFD00AC73B3 /* stickersend72@2x.png */; };
+ 298990611F1B6FFD00AC73B3 /* stickersend73@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F6F1F1B6FFD00AC73B3 /* stickersend73@2x.png */; };
+ 298990621F1B6FFD00AC73B3 /* stickersend74@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F701F1B6FFD00AC73B3 /* stickersend74@2x.png */; };
+ 298990631F1B6FFD00AC73B3 /* stickersend75@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F711F1B6FFD00AC73B3 /* stickersend75@2x.png */; };
+ 298990641F1B6FFD00AC73B3 /* stickersend76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F721F1B6FFD00AC73B3 /* stickersend76@2x.png */; };
+ 298990651F1B6FFD00AC73B3 /* stickersend77@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F731F1B6FFD00AC73B3 /* stickersend77@2x.png */; };
+ 298990661F1B6FFD00AC73B3 /* stickersend78@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29898F741F1B6FFD00AC73B3 /* stickersend78@2x.png */; };
+ 298990671F1B6FFD00AC73B3 /* wallpapers01@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 29898F761F1B6FFD00AC73B3 /* wallpapers01@2x.jpg */; };
+ 298990681F1B6FFD00AC73B3 /* wallpapers02@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 29898F771F1B6FFD00AC73B3 /* wallpapers02@2x.jpg */; };
+ 298990691F1B6FFD00AC73B3 /* wallpapers03@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 29898F781F1B6FFD00AC73B3 /* wallpapers03@2x.jpg */; };
+ 2989906A1F1B6FFD00AC73B3 /* wallpapers04@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 29898F791F1B6FFD00AC73B3 /* wallpapers04@2x.jpg */; };
+ 2989906B1F1B6FFD00AC73B3 /* wallpapers05@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 29898F7A1F1B6FFD00AC73B3 /* wallpapers05@2x.jpg */; };
+ 2989906C1F1B6FFD00AC73B3 /* wallpapers06@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 29898F7B1F1B6FFD00AC73B3 /* wallpapers06@2x.jpg */; };
+ 2989906D1F1B6FFD00AC73B3 /* wallpapers07@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 29898F7C1F1B6FFD00AC73B3 /* wallpapers07@2x.jpg */; };
+ 2989906E1F1B6FFD00AC73B3 /* wallpapers08@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 29898F7D1F1B6FFD00AC73B3 /* wallpapers08@2x.jpg */; };
+ 2989906F1F1B6FFD00AC73B3 /* wallpapers09@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 29898F7E1F1B6FFD00AC73B3 /* wallpapers09@2x.jpg */; };
+ 298990701F1B6FFD00AC73B3 /* wallpapers10@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 29898F7F1F1B6FFD00AC73B3 /* wallpapers10@2x.jpg */; };
+ 298990711F1B6FFD00AC73B3 /* privacy.html in Resources */ = {isa = PBXBuildFile; fileRef = 29898F811F1B6FFD00AC73B3 /* privacy.html */; };
+ 298990721F1B6FFD00AC73B3 /* terms.html in Resources */ = {isa = PBXBuildFile; fileRef = 29898F821F1B6FFD00AC73B3 /* terms.html */; };
+ 299702621F9CC88900830794 /* Friend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 299702611F9CC88900830794 /* Friend.swift */; };
+ 29AA54BD20D3B3DA004067FE /* RelayManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AA54BB20D3B3D9004067FE /* RelayManager.swift */; };
+ 29B72CC720EE93A900E4C078 /* FObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B72CC620EE93A900E4C078 /* FObject.swift */; };
+ 29BB8F871F5C266100560F3F /* addaccount_logo@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F241F5C266100560F3F /* addaccount_logo@2x.png */; };
+ 29BB8F881F5C266100560F3F /* advert01@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F261F5C266100560F3F /* advert01@2x.png */; };
+ 29BB8F891F5C266100560F3F /* advert02@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F271F5C266100560F3F /* advert02@2x.png */; };
+ 29BB8F8A1F5C266100560F3F /* advert03@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F281F5C266100560F3F /* advert03@2x.png */; };
+ 29BB8F8B1F5C266100560F3F /* advert04@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F291F5C266100560F3F /* advert04@2x.png */; };
+ 29BB8F8C1F5C266100560F3F /* advert05@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F2A1F5C266100560F3F /* advert05@2x.png */; };
+ 29BB8F8D1F5C266100560F3F /* advert06@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F2B1F5C266100560F3F /* advert06@2x.png */; };
+ 29BB8F8E1F5C266100560F3F /* advert07@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F2C1F5C266100560F3F /* advert07@2x.png */; };
+ 29BB8F8F1F5C266100560F3F /* advert08@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F2D1F5C266100560F3F /* advert08@2x.png */; };
+ 29BB8F901F5C266100560F3F /* advert09@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F2E1F5C266100560F3F /* advert09@2x.png */; };
+ 29BB8F911F5C266100560F3F /* advert10@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F2F1F5C266100560F3F /* advert10@2x.png */; };
+ 29BB8F921F5C266100560F3F /* advert11@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F301F5C266100560F3F /* advert11@2x.png */; };
+ 29BB8F931F5C266100560F3F /* allmedia_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F321F5C266100560F3F /* allmedia_blank@2x.png */; };
+ 29BB8F941F5C266100560F3F /* allmedia_selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F331F5C266100560F3F /* allmedia_selected@2x.png */; };
+ 29BB8F951F5C266100560F3F /* allmedia_video@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F341F5C266100560F3F /* allmedia_video@2x.png */; };
+ 29BB8F961F5C266100560F3F /* archive_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F361F5C266100560F3F /* archive_blank@2x.png */; };
+ 29BB8F971F5C266100560F3F /* archive_muted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F371F5C266100560F3F /* archive_muted@2x.png */; };
+ 29BB8F981F5C266100560F3F /* blocked_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F391F5C266100560F3F /* blocked_blank@2x.png */; };
+ 29BB8F991F5C266100560F3F /* callaudio_answer@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F3B1F5C266100560F3F /* callaudio_answer@2x.png */; };
+ 29BB8F9A1F5C266100560F3F /* callaudio_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F3C1F5C266100560F3F /* callaudio_blank@2x.png */; };
+ 29BB8F9B1F5C266100560F3F /* callaudio_hangup@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F3D1F5C266100560F3F /* callaudio_hangup@2x.png */; };
+ 29BB8F9C1F5C266100560F3F /* callaudio_mute1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F3E1F5C266100560F3F /* callaudio_mute1@2x.png */; };
+ 29BB8F9D1F5C266100560F3F /* callaudio_mute2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F3F1F5C266100560F3F /* callaudio_mute2@2x.png */; };
+ 29BB8F9E1F5C266100560F3F /* callaudio_speaker1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F401F5C266100560F3F /* callaudio_speaker1@2x.png */; };
+ 29BB8F9F1F5C266100560F3F /* callaudio_speaker2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F411F5C266100560F3F /* callaudio_speaker2@2x.png */; };
+ 29BB8FA01F5C266100560F3F /* callaudio_video1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F421F5C266100560F3F /* callaudio_video1@2x.png */; };
+ 29BB8FA11F5C266100560F3F /* callaudio_video2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F431F5C266100560F3F /* callaudio_video2@2x.png */; };
+ 29BB8FA21F5C266100560F3F /* callvideo_answer@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F451F5C266100560F3F /* callvideo_answer@2x.png */; };
+ 29BB8FA31F5C266100560F3F /* callvideo_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F461F5C266100560F3F /* callvideo_blank@2x.png */; };
+ 29BB8FA41F5C266100560F3F /* callvideo_hangup@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F471F5C266100560F3F /* callvideo_hangup@2x.png */; };
+ 29BB8FA51F5C266100560F3F /* callvideo_mute1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F481F5C266100560F3F /* callvideo_mute1@2x.png */; };
+ 29BB8FA61F5C266100560F3F /* callvideo_mute2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F491F5C266100560F3F /* callvideo_mute2@2x.png */; };
+ 29BB8FA71F5C266100560F3F /* callvideo_switch1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F4A1F5C266100560F3F /* callvideo_switch1@2x.png */; };
+ 29BB8FA81F5C266100560F3F /* callvideo_switch2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F4B1F5C266100560F3F /* callvideo_switch2@2x.png */; };
+ 29BB8FA91F5C266100560F3F /* chat_back@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F4D1F5C266100560F3F /* chat_back@2x.png */; };
+ 29BB8FAA1F5C266100560F3F /* chat_callaudio@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F4E1F5C266100560F3F /* chat_callaudio@2x.png */; };
+ 29BB8FAB1F5C266100560F3F /* chat_callvideo@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F4F1F5C266100560F3F /* chat_callvideo@2x.png */; };
+ 29BB8FAC1F5C266100560F3F /* chat_camera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F501F5C266100560F3F /* chat_camera@2x.png */; };
+ 29BB8FAD1F5C266100560F3F /* chat_location@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F511F5C266100560F3F /* chat_location@2x.png */; };
+ 29BB8FAE1F5C266100560F3F /* chat_picture@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F521F5C266100560F3F /* chat_picture@2x.png */; };
+ 29BB8FAF1F5C266100560F3F /* chat_sticker@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F531F5C266100560F3F /* chat_sticker@2x.png */; };
+ 29BB8FB01F5C266100560F3F /* chat_video@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F541F5C266100560F3F /* chat_video@2x.png */; };
+ 29BB8FB11F5C266100560F3F /* chats_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F561F5C266100560F3F /* chats_blank@2x.png */; };
+ 29BB8FB21F5C266100560F3F /* chats_muted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F571F5C266100560F3F /* chats_muted@2x.png */; };
+ 29BB8FB41F5C266100560F3F /* editprofile_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F5B1F5C266100560F3F /* editprofile_blank@2x.png */; };
+ 29BB8FB81F5C266100560F3F /* launchscreen_logo@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F621F5C266100560F3F /* launchscreen_logo@2x.png */; };
+ 29BB8FB91F5C266100560F3F /* login_logo@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F641F5C266100560F3F /* login_logo@2x.png */; };
+ 29BB8FBA1F5C266100560F3F /* people_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F661F5C266100560F3F /* people_blank@2x.png */; };
+ 29BB8FBB1F5C266100560F3F /* profile_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F681F5C266100560F3F /* profile_blank@2x.png */; };
+ 29BB8FBC1F5C266100560F3F /* profile_callaudio@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F691F5C266100560F3F /* profile_callaudio@2x.png */; };
+ 29BB8FBD1F5C266100560F3F /* profile_callvideo@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F6A1F5C266100560F3F /* profile_callvideo@2x.png */; };
+ 29BB8FBE1F5C266100560F3F /* register_logo@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F6C1F5C266100560F3F /* register_logo@2x.png */; };
+ 29BB8FC11F5C266100560F3F /* selectusers_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F721F5C266100560F3F /* selectusers_blank@2x.png */; };
+ 29BB8FC21F5C266100560F3F /* selectuser_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F741F5C266100560F3F /* selectuser_blank@2x.png */; };
+ 29BB8FC41F5C266100560F3F /* settings_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F781F5C266100560F3F /* settings_blank@2x.png */; };
+ 29BB8FC51F5C266100560F3F /* stickers_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F7A1F5C266100560F3F /* stickers_blank@2x.png */; };
+ 29BB8FC61F5C266100560F3F /* switchaccount_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F7C1F5C266100560F3F /* switchaccount_blank@2x.png */; };
+ 29BB8FC71F5C266100560F3F /* tab_calls@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F7E1F5C266100560F3F /* tab_calls@2x.png */; };
+ 29BB8FC81F5C266100560F3F /* tab_chats@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F7F1F5C266100560F3F /* tab_chats@2x.png */; };
+ 29BB8FCA1F5C266100560F3F /* tab_people@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F811F5C266100560F3F /* tab_people@2x.png */; };
+ 29BB8FCB1F5C266100560F3F /* tab_settings@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F821F5C266100560F3F /* tab_settings@2x.png */; };
+ 29BB8FCC1F5C266100560F3F /* wallpaper_selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F841F5C266100560F3F /* wallpaper_selected@2x.png */; };
+ 29BB8FCD1F5C266100560F3F /* welcome_logo@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29BB8F861F5C266100560F3F /* welcome_logo@2x.png */; };
+ 29C299631F8A647B00DB8BF0 /* addfriends_blank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29C299621F8A647B00DB8BF0 /* addfriends_blank@2x.png */; };
+ 29C4BD3A2095EA900047886E /* UserStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C4BD382095EA900047886E /* UserStatus.swift */; };
+ 29CAE58720971787000D857D /* ChatGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CAE58620971786000D857D /* ChatGroupView.swift */; };
+ 29D29EF91D9A59E4006CA074 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D29EF81D9A59E4006CA074 /* AppDelegate.swift */; };
+ 29D29F011D9A59E4006CA074 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 29D29F001D9A59E4006CA074 /* Assets.xcassets */; };
+ 29D29F041D9A59E4006CA074 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 29D29F021D9A59E4006CA074 /* LaunchScreen.storyboard */; };
+ 29D7605320EFD49D006C7148 /* RCMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7602020EFD49D006C7148 /* RCMenuItem.swift */; };
+ 29D7605420EFD49D006C7148 /* RCTextMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7602620EFD49D006C7148 /* RCTextMessageCell.swift */; };
+ 29D7605520EFD49D006C7148 /* RCEmojiMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7602820EFD49D006C7148 /* RCEmojiMessageCell.swift */; };
+ 29D7605620EFD49D006C7148 /* RCMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7602A20EFD49D006C7148 /* RCMessageCell.swift */; };
+ 29D7605720EFD49D006C7148 /* RCPictureMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7602D20EFD49D006C7148 /* RCPictureMessageCell.swift */; };
+ 29D7605820EFD49D006C7148 /* RCVideoMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7603020EFD49D006C7148 /* RCVideoMessageCell.swift */; };
+ 29D7605920EFD49D006C7148 /* RCAudioMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7603320EFD49D006C7148 /* RCAudioMessageCell.swift */; };
+ 29D7605A20EFD49D006C7148 /* RCLocationMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7603620EFD49D006C7148 /* RCLocationMessageCell.swift */; };
+ 29D7605B20EFD49D006C7148 /* RCStatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7603920EFD49D006C7148 /* RCStatusCell.swift */; };
+ 29D7605C20EFD49D006C7148 /* RCSectionFooterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7603E20EFD49D006C7148 /* RCSectionFooterCell.swift */; };
+ 29D7605D20EFD49D006C7148 /* RCBubbleFooterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7603F20EFD49D006C7148 /* RCBubbleFooterCell.swift */; };
+ 29D7605E20EFD49D006C7148 /* RCSectionHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7604120EFD49D006C7148 /* RCSectionHeaderCell.swift */; };
+ 29D7605F20EFD49E006C7148 /* RCBubbleHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7604220EFD49D006C7148 /* RCBubbleHeaderCell.swift */; };
+ 29D7606C20EFD4DF006C7148 /* rcmessage_send@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29D7606420EFD4DF006C7148 /* rcmessage_send@2x.png */; };
+ 29D7606D20EFD4DF006C7148 /* rcmessages_audiopause@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29D7606520EFD4DF006C7148 /* rcmessages_audiopause@2x.png */; };
+ 29D7606E20EFD4DF006C7148 /* rcmessage_attach@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29D7606620EFD4DF006C7148 /* rcmessage_attach@2x.png */; };
+ 29D7606F20EFD4DF006C7148 /* rcmessages_videoplay@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29D7606720EFD4DF006C7148 /* rcmessages_videoplay@2x.png */; };
+ 29D7607020EFD4DF006C7148 /* rcmessage_outgoing.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 29D7606820EFD4DF006C7148 /* rcmessage_outgoing.aiff */; };
+ 29D7607120EFD4DF006C7148 /* rcmessages_manual@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29D7606920EFD4DF006C7148 /* rcmessages_manual@2x.png */; };
+ 29D7607220EFD4DF006C7148 /* rcmessages_audioplay@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29D7606A20EFD4DF006C7148 /* rcmessages_audioplay@2x.png */; };
+ 29D7607320EFD4DF006C7148 /* rcmessage_incoming.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 29D7606B20EFD4DF006C7148 /* rcmessage_incoming.aiff */; };
+ 29D7607A20EFD552006C7148 /* RCMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D7607520EFD552006C7148 /* RCMessagesView.swift */; };
+ 29D7607B20EFD552006C7148 /* RCMessagesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29D7607720EFD552006C7148 /* RCMessagesView.xib */; };
+ 29DC391820EF6004004E41BE /* FUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DC391720EF6004004E41BE /* FUser.swift */; };
+ 29DC391A20EF76E1004E41BE /* FUser+Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DC391920EF76E1004E41BE /* FUser+Util.swift */; };
+ 29E5AD55209EF2A000AC2E67 /* UploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E5AD53209EF29F00AC2E67 /* UploadManager.swift */; };
+ 29E76AC420DE517B00572BBC /* GroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E76AC320DE517B00572BBC /* GroupView.swift */; };
+ 29EABE4E20ECAC3C00A13F26 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EABE4C20ECAC3C00A13F26 /* NavigationController.swift */; };
+ 29FAEF4E20F396FC00E75BCD /* RCMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FAEF4D20F396FC00E75BCD /* RCMessage.swift */; };
+ 29FAEF5020F39A7A00E75BCD /* RCMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FAEF4F20F39A7A00E75BCD /* RCMessages.swift */; };
+ 6C0888E250EDDE30BCA104EB /* Pods_app.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14A8835041C05399266378E9 /* Pods_app.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 14A8835041C05399266378E9 /* Pods_app.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_app.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 290D1A071F8BCDCB00CFBFB0 /* DBBlocker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DBBlocker.swift; sourceTree = ""; };
+ 290D1A0A1F8BCEB800CFBFB0 /* Blockers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blockers.swift; sourceTree = ""; };
+ 290D1A0C1F8BD39100CFBFB0 /* Blocker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blocker.swift; sourceTree = ""; };
+ 29104BBE20EFC21B003BD623 /* RCAudioPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RCAudioPlayer.swift; sourceTree = ""; };
+ 2917DE1920ECA4D500EBCD97 /* NSDictionary+Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSDictionary+Util.swift"; sourceTree = ""; };
+ 291CB49A202F7E460078D0B0 /* chat_audio@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "chat_audio@2x.png"; sourceTree = ""; };
+ 2920B1822030AD3500DB6E07 /* AudioView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AudioView.xib; sourceTree = "