484 lines
21 KiB
Swift
484 lines
21 KiB
Swift
//
|
|
// CryptoChatControllerChat.swift
|
|
// PayCash
|
|
//
|
|
// Created by Saveliy Stavitsky on 8/19/20.
|
|
// Copyright © 2020 List. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import MessageKit
|
|
import InputBarAccessoryView
|
|
import EosioSwift
|
|
import IQKeyboardManagerSwift
|
|
|
|
import struct RealmSwift.Results
|
|
|
|
extension MessageCollectionViewCell {
|
|
|
|
override open func delete(_ sender: Any?) {
|
|
|
|
// Get the collectionView
|
|
if let collectionView = self.superview as? UICollectionView {
|
|
// Get indexPath
|
|
if let indexPath = collectionView.indexPath(for: self) {
|
|
// Trigger action
|
|
collectionView.delegate?.collectionView?(collectionView, performAction: NSSelectorFromString("delete:"), forItemAt: indexPath, withSender: sender)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var lastMsgs: [String: String] = [:]
|
|
|
|
class CryptoChatControllerChat: MessagesViewController {
|
|
|
|
var username: String!
|
|
var unreadCount = 0
|
|
var msgsHistoryService: CryptoChat.Service.MsgsHistory!
|
|
|
|
var loadedMsgsCount = 0
|
|
let refreshControl = UIRefreshControl()
|
|
lazy var messages: Results<CryptoChatModelRealmMessage> = { msgsHistoryService.openChatWith(username) }()
|
|
|
|
var bottomSpace: CGFloat = 0
|
|
|
|
private lazy var createButton: UIButton = {
|
|
let button = UIButton(width: UIScreen.main.bounds.width, height: 32)
|
|
button.frameOrigin = CGPoint(x: 0, y: 44)
|
|
button.titleLabel?.font = FontFamily.GolosUI.medium.font(size: 12)
|
|
button.setImage(Asset.walletInheritanceRefresh.image.withRenderingMode(.alwaysTemplate).tinted(with: .white), for: .normal)
|
|
button.setTitle(L10n.CryptoChat.Chat.refresh, for: .normal)
|
|
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: -16)
|
|
button.backgroundColor = Asset.dark.color
|
|
button.addTarget(self, action: #selector(self.refresh(_:)), for: .touchUpInside)
|
|
button.contentHorizontalAlignment = .center
|
|
button.setTitleColor(.white, for: .normal)
|
|
|
|
return button
|
|
}()
|
|
|
|
var topbarHeight: CGFloat {
|
|
UIApplication.shared.statusBarFrame.size.height +
|
|
(self.navigationController?.navigationBar.frame.size.height ?? 0.0)
|
|
}
|
|
|
|
private lazy var buttonsView: CryptoChatButtonsArrayView = {
|
|
let buttonsView = CryptoChatButtonsArrayView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 56))
|
|
// buttonsView.frameOrigin = CGPoint(x: 0, y: 0)
|
|
buttonsView.qrPressed = { [weak self] in self?.showQrPopup() }
|
|
buttonsView.transferPressed = { [weak self] in
|
|
let viewController = StoryboardScene.Wallet.transfer.instantiate()
|
|
viewController.hidesBack = true
|
|
var dictionary: [String: Any] = [:]
|
|
dictionary["address"] = self?.username ?? ""
|
|
viewController.dictionary = dictionary
|
|
self?.navigationController?.pushViewController(viewController, animated: true)
|
|
}
|
|
buttonsView.receivePressed = { [weak self] in
|
|
let viewController = StoryboardScene.Wallet.receive.instantiate()
|
|
viewController.hidesBack = true
|
|
self?.navigationController?.pushViewController(viewController, animated: true)
|
|
}
|
|
return buttonsView
|
|
}()
|
|
|
|
let infoView = CommonViewWarning()
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
messagesCollectionView.register(InfoHeaderCollectionReusableView.self,
|
|
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader)
|
|
|
|
// view.addSubview(createButton)
|
|
view.addSubview(buttonsView)
|
|
|
|
let navigationItem = UINavigationItem()
|
|
navigationItem.title = username
|
|
navigationItem.leftBarButtonItem = .pop(self)
|
|
navigationItem.rightBarButtonItem =
|
|
UIBarButtonItem(customView:
|
|
(UIApplication.shared.windows.first!.rootViewController as? MainController)!.resourcesSwitch)
|
|
(UIApplication.shared.windows.first!.rootViewController as? MainController)?
|
|
.barNav.pushItem(navigationItem, animated: true)
|
|
|
|
infoView.text = L10n.CryptoChat.Chat.info
|
|
view.addSubview(infoView)
|
|
infoView.translatesAutoresizingMaskIntoConstraints = false
|
|
infoView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
|
|
infoView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
|
|
|
|
msgsHistoryService?.didUpdate = { [weak self] in
|
|
guard let self = self else { return }
|
|
DispatchQueue.main.async {
|
|
self.loadedMsgsCount = min(self.messages.count, self.loadedMsgsCount + 100)
|
|
self.messagesCollectionView.reloadData()
|
|
self.messagesCollectionView.scrollToBottom()
|
|
|
|
self.msgsHistoryService?.markChatAsRead(username: self.username)
|
|
}
|
|
}
|
|
msgsHistoryService.markChatAsRead(username: username)
|
|
|
|
navigationItem.title = username
|
|
navigationItem.leftBarButtonItem = .pop(self)
|
|
// navigationItem.rightBarButtonItem = .info {
|
|
// Alert.hint(text: L10n.CryptoChat.Chat.infoHelpText)
|
|
// }
|
|
|
|
messagesCollectionView.messagesDataSource = self
|
|
messagesCollectionView.messagesLayoutDelegate = self
|
|
messagesCollectionView.messagesDisplayDelegate = self
|
|
|
|
// let tap = UITapGestureRecognizer(target: self, action: #selector(CryptoChatControllerChat.dismissKeyboard))
|
|
// messagesCollectionView.addGestureRecognizer(tap)
|
|
messagesCollectionView.keyboardDismissMode = .onDrag
|
|
|
|
navigationController?.navigationBar.barTintColor = .white
|
|
self.messagesCollectionView.backgroundColor = .white
|
|
|
|
configureMessageCollectionView()
|
|
configureMessageInputBar()
|
|
|
|
messagesCollectionView.scrollToBottom()
|
|
|
|
messagesCollectionView.addSubview(refreshControl)
|
|
refreshControl.addTarget(self, action: #selector(loadMoreMessages), for: .valueChanged)
|
|
|
|
observers.append(contentsOf: [
|
|
Notification.subscribe(name: .didUpdateHistory, { [weak self] in
|
|
guard Accounts().current?.username == $0.userInfo?["username"] as? String else { return }
|
|
guard let self = self else { return }
|
|
self.msgsHistoryService?.fetchFromLocalHistory()
|
|
})
|
|
])
|
|
}
|
|
|
|
var observers: [AnyObject] = []
|
|
deinit { observers.forEach({ Notification.unsubscribe(observer: $0) }) }
|
|
|
|
@objc func dismissKeyboard() {
|
|
self.messageInputBar.inputTextView.resignFirstResponder()
|
|
}
|
|
|
|
@objc func refresh(_ sender: AnyObject) {
|
|
Account.Service.History.shared.current?.fetch()
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
Accounts().isLocked = true
|
|
IQKeyboardManager.shared.enable = false
|
|
self.becomeFirstResponder()
|
|
|
|
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
|
|
self.navigationController?.setNavigationBarHidden(true, animated: false)
|
|
|
|
messageInputBar.inputTextView.text = lastMsgs[username] ?? ""
|
|
}
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
|
|
self.becomeFirstResponder()
|
|
}
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
Accounts().isLocked = false
|
|
IQKeyboardManager.shared.enable = true
|
|
|
|
self.navigationController?.setNavigationBarHidden(false, animated: false)
|
|
|
|
lastMsgs[username] = messageInputBar.inputTextView.text
|
|
}
|
|
|
|
@objc func loadMoreMessages() {
|
|
loadedMsgsCount = min(messages.count, loadedMsgsCount + 100)
|
|
self.messagesCollectionView.reloadDataAndKeepOffset()
|
|
self.refreshControl.endRefreshing()
|
|
}
|
|
|
|
override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
|
|
|
|
if action == NSSelectorFromString("delete:") {
|
|
return true
|
|
} else {
|
|
return super.collectionView(collectionView, canPerformAction: action, forItemAt: indexPath, withSender: sender)
|
|
}
|
|
}
|
|
|
|
override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
|
|
|
|
if action == NSSelectorFromString("delete:") {
|
|
// 1.) Remove from datasource
|
|
// insert your code here
|
|
let msg = messages[messages.count - loadedMsgsCount + indexPath.section]
|
|
self.msgsHistoryService.hideMsg(ephemPublicKey: msg.id)
|
|
self.loadedMsgsCount -= 1
|
|
|
|
// 2.) Delete sections
|
|
collectionView.deleteSections([indexPath.section])
|
|
self.messagesCollectionView.reloadData()
|
|
} else {
|
|
super.collectionView(collectionView, performAction: action, forItemAt: indexPath, withSender: sender)
|
|
}
|
|
}
|
|
|
|
func configureMessageCollectionView() {
|
|
|
|
messagesCollectionView.messagesDataSource = self
|
|
messagesCollectionView.messageCellDelegate = self
|
|
|
|
scrollsToBottomOnKeyboardBeginsEditing = true // default false
|
|
maintainPositionOnKeyboardFrameChanged = true // default false
|
|
|
|
if let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout {
|
|
layout.setMessageIncomingAccessoryViewSize(CGSize(width: 24, height: 24))
|
|
layout.setMessageIncomingAccessoryViewPadding(HorizontalEdgeInsets(left: 20, right: 0))
|
|
layout.setMessageIncomingAvatarSize(.zero)
|
|
layout.setMessageIncomingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 0)))
|
|
layout.setMessageIncomingCellTopLabelAlignment(LabelAlignment(textAlignment: .center, textInsets: .zero))
|
|
layout.setMessageIncomingCellBottomLabelAlignment(LabelAlignment(textAlignment: .center, textInsets: .zero))
|
|
|
|
layout.setMessageOutgoingAccessoryViewSize(CGSize(width: 24, height: 24))
|
|
layout.setMessageOutgoingAccessoryViewPadding(HorizontalEdgeInsets(left: 0, right: 20))
|
|
layout.setMessageOutgoingAvatarSize(.zero)
|
|
layout.setMessageOutgoingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
|
|
layout.setMessageOutgoingCellTopLabelAlignment(LabelAlignment(textAlignment: .center, textInsets: .zero))
|
|
layout.setMessageOutgoingCellBottomLabelAlignment(LabelAlignment(textAlignment: .center, textInsets: .zero))
|
|
}
|
|
}
|
|
|
|
func configureMessageInputBar() {
|
|
messageInputBar.delegate = self
|
|
messageInputBar.sendButton.image = Asset.chatSend.image
|
|
messageInputBar.sendButton.title = nil
|
|
messageInputBar.inputTextView.placeholderTextColor = Asset.textPebble.color
|
|
messageInputBar.inputTextView.textColor = Asset.textGranite.color
|
|
messageInputBar.inputTextView.font = Font.font(style: .regular, size: 16)
|
|
// messageInputBar.inputTextView.layer.cornerRadius = 12.0
|
|
// messageInputBar.inputTextView.layer.borderWidth = 1.0
|
|
// messageInputBar.inputTextView.layer.borderColor = Asset.lightGray.color.cgColor
|
|
// messageInputBar.separatorLine.isHidden = true
|
|
messageInputBar.separatorLine.backgroundColor = Asset.marble.color
|
|
messageInputBar.textViewPadding = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0)
|
|
messageInputBar.maxTextViewHeight = 140
|
|
// messageInputBar.inputTextView.textContainerInset = UIEdgeInsets(top: 5, left: 12, bottom: 4, right: 32)
|
|
// messageInputBar.inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 5, left: 16, bottom: 4, right: 12)
|
|
messageInputBar.inputTextView.placeholder = L10n.CryptoChat.Chat.emptyInputPlaceholder
|
|
messageInputBar.backgroundView.backgroundColor = .white
|
|
messageInputBar.inputTextView.isImagePasteEnabled = false
|
|
|
|
return ()
|
|
|
|
let items = [
|
|
InputBarButtonItem()
|
|
.configure {
|
|
$0.spacing = .fixed(20)
|
|
$0.image = Asset.chatQr.image
|
|
$0.setSize(CGSize(width: 32, height: 32), animated: false)
|
|
}.onSelected { [weak self] _ in
|
|
self?.showQrPopup()
|
|
}
|
|
// .onDeselected {
|
|
// $0.tintColor = UIColor.lightGray
|
|
// }
|
|
,
|
|
messageInputBar
|
|
.sendButton
|
|
.configure {
|
|
$0.spacing = .fixed(0)
|
|
$0.image = Asset.chatSend.image
|
|
$0.title = nil
|
|
$0.setSize(CGSize(width: 32, height: 32), animated: false)
|
|
}
|
|
]
|
|
messageInputBar.middleContentViewPadding.right = -46
|
|
messageInputBar.setRightStackViewWidthConstant(to: 82 , animated: true)
|
|
messageInputBar.setStackViewItems(items, forStack: .right, animated: true)
|
|
messageInputBar.invalidateIntrinsicContentSize()
|
|
reloadInputViews()
|
|
messageInputBar.layoutIfNeeded()
|
|
}
|
|
|
|
private func showQrPopup() {
|
|
guard UIImagePickerController.isSourceTypeAvailable(.photoLibrary) else {
|
|
let ctrl = ScannerViewController()
|
|
ctrl.navigationItem.leftBarButtonItem = .close { [weak self] in
|
|
DispatchQueue.main.async {
|
|
self?.dismiss(animated: true)
|
|
}
|
|
}
|
|
|
|
ctrl.didCapture = { [weak self] string in
|
|
DispatchQueue.main.async {
|
|
self?.dismiss(animated: true)
|
|
}
|
|
self?.sendMsg(msgText: "<qr>\(string)</qr>")
|
|
}
|
|
let navCtrl = UINavigationController(rootViewController: ctrl)
|
|
navCtrl.modalPresentationStyle = .fullScreen
|
|
self.navigationController?.tabBarController?.present(navCtrl, animated: true)
|
|
|
|
return
|
|
}
|
|
|
|
let ctrl = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
|
ctrl.addAction(.init(title: L10n.Wallet.Send.qrCamera, style: .default) { [weak self] _ in
|
|
let ctrl = ScannerViewController()
|
|
ctrl.navigationItem.leftBarButtonItem = .close { [weak self] in
|
|
DispatchQueue.main.async {
|
|
self?.dismiss(animated: true)
|
|
}
|
|
}
|
|
|
|
ctrl.didCapture = { [weak self] string in
|
|
DispatchQueue.main.async {
|
|
self?.dismiss(animated: true)
|
|
}
|
|
self?.sendMsg(msgText: "<qr>\(string)</qr>")
|
|
}
|
|
let navCtrl = UINavigationController(rootViewController: ctrl)
|
|
navCtrl.modalPresentationStyle = .fullScreen
|
|
self?.navigationController?.tabBarController?.present(navCtrl, animated: true)
|
|
})
|
|
|
|
ctrl.addAction(.init(title: L10n.Wallet.Send.qrLibrary, style: .default) { [weak self] _ in
|
|
let imagePicker = UIImagePickerController()
|
|
imagePicker.delegate = self
|
|
imagePicker.sourceType = .photoLibrary
|
|
self?.present(imagePicker, animated: true, completion: nil)
|
|
})
|
|
ctrl.addAction(.init(title: L10n.Common.Button.cancel, style: .cancel, handler: nil))
|
|
present(ctrl, animated: true)
|
|
}
|
|
}
|
|
|
|
extension CryptoChatControllerChat: InputBarAccessoryViewDelegate {
|
|
|
|
func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
|
|
let components = inputBar.inputTextView.components
|
|
messageInputBar.inputTextView.text = String()
|
|
messageInputBar.invalidatePlugins()
|
|
|
|
var msgText = ""
|
|
for component in components {
|
|
if let text = component as? String {
|
|
msgText += "<plaintext>\(text)</plaintext>"
|
|
} else if let img = (component as? UIImage)?.pngData() {
|
|
msgText += "<image>\(img.base64EncodedString())</image>"
|
|
}
|
|
}
|
|
|
|
sendMsg(msgText: msgText)
|
|
}
|
|
|
|
func sendMsg(msgText: String) {
|
|
guard let username = username, username.count > 0 else { return }
|
|
guard let user = Accounts().current else { return }
|
|
|
|
// Send button activity animation
|
|
messageInputBar.sendButton.startAnimating()
|
|
messageInputBar.inputTextView.placeholder = L10n.CryptoChat.Chat.sendingInputPlaceholder
|
|
EosioRpcProvider(endpoint: URL(string: Network.servers.current.node)!).getAccount(requestParameters: EosioRpcAccountRequest(accountName: username), completion: { [weak self] in
|
|
guard let self = self else { return }
|
|
self.messageInputBar.sendButton.stopAnimating()
|
|
self.messageInputBar.inputTextView.placeholder = L10n.CryptoChat.Chat.emptyInputPlaceholder
|
|
// self.insertMessages(components)
|
|
self.messagesCollectionView.scrollToBottom(animated: true)
|
|
|
|
switch $0 {
|
|
case let .success(account):
|
|
var receiverPublicKey: String = ""
|
|
account.permissions.forEach({
|
|
if $0.permName == "me.chat" {
|
|
if let key = $0.requiredAuth.keys.first?.key, $0.requiredAuth.keys.count > 0 {
|
|
receiverPublicKey = key
|
|
} else {
|
|
Alert.error(text: "Found more than one key in me.chat permissoin of \(username)")
|
|
}
|
|
}
|
|
})
|
|
|
|
if receiverPublicKey.count == 0 {
|
|
print("Could not find me.chat permission, looking up active key")
|
|
account.permissions.forEach({
|
|
if $0.permName == "active" {
|
|
if let key = $0.requiredAuth.keys.first?.key, $0.requiredAuth.keys.count > 0 {
|
|
receiverPublicKey = key
|
|
} else {
|
|
Alert.error(text: "Found more than one key in active permissoin of \(username)")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
if receiverPublicKey.count == 0 {
|
|
Alert.error(text: "Cannot find me.chat or active keys for account \(username)")
|
|
return
|
|
}
|
|
|
|
do {
|
|
let msg = try CryptoChat.Model.Msg(from: user.username, to: username,
|
|
msg: msgText, receiverPublicKey: receiverPublicKey)
|
|
|
|
AccountViewAuthorize.showGetPrivateKey(in: self) {
|
|
self.msgsHistoryService.saveMsg(CryptoChatModelRealmMessage(id: msg.ephemPublicKey, from: msg.from, to: msg.to, chatName: username, timestamp: Date(), data: msgText), ignoreTimestamp: true)
|
|
self.loadedMsgsCount += 1
|
|
self.messagesCollectionView.reloadData()
|
|
self.messagesCollectionView.scrollToBottom()
|
|
|
|
Network.Service.Blockchain.execute(contract: .chat, action: .chatSendDm, data: msg, privateKeys: [$0]) {
|
|
switch $0 {
|
|
case let .success(trxId):
|
|
// UserDefaults.standard.setValue(<#T##value: Any?##Any?#>, forKey: "chatsReadCount")
|
|
// AccountHistory().current?.fetchUntil(trxId: trxId)
|
|
self.msgsHistoryService.setMsgSynced(ephemPublicKey: msg.ephemPublicKey)
|
|
self.msgsHistoryService.didUpdate?()
|
|
case let .failure(error):
|
|
Alert.error(text: "\(error)")
|
|
self.msgsHistoryService.markMsgUnsent(ephemPublicKey: msg.ephemPublicKey)
|
|
self.msgsHistoryService.didUpdate?()
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
Alert.error(text: "Error while encrypt msg: \(error.localizedDescription)")
|
|
}
|
|
case let .failure(error):
|
|
Alert.error(text: error.description)
|
|
}
|
|
})
|
|
}
|
|
|
|
private func insertMessages(_ data: [Any]) {
|
|
}
|
|
}
|
|
|
|
extension CryptoChatControllerChat: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
|
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
|
self.dismiss(animated: true, completion: nil)
|
|
|
|
if let selectedImage = info[.originalImage] as? UIImage,
|
|
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]),
|
|
let ciImage = CIImage(image: selectedImage) {
|
|
var qrCodeLink = ""
|
|
for feature in (detector.features(in: ciImage) as? [CIQRCodeFeature]) ?? [] {
|
|
qrCodeLink += feature.messageString ?? ""
|
|
}
|
|
|
|
print(qrCodeLink)//Your result from QR Code
|
|
if !qrCodeLink.isEmpty {
|
|
sendMsg(msgText: "<qr>\(qrCodeLink)</qr>")
|
|
} else {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
|
|
Alert.error(text: L10n.CryptoChat.Chat.noQr)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|