533 lines
20 KiB
Swift
533 lines
20 KiB
Swift
//
|
|
// CryptoChatControllerChats.swift
|
|
// Wallet
|
|
//
|
|
// Created by Saveliy Stavitsky on 8/17/20.
|
|
// Copyright © 2020 List. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import IQKeyboardManagerSwift
|
|
import EosioSwift
|
|
import Branch
|
|
import Combine
|
|
import WalletKit
|
|
|
|
import struct RealmSwift.Results
|
|
|
|
final class CryptoChatControllerChats: UIViewController {
|
|
|
|
@IBOutlet private weak var tableView: UITableView!
|
|
@IBOutlet weak var headerContainerView: UIView!
|
|
@IBOutlet weak var addChatButton: UIButton!
|
|
@IBOutlet weak var chatsListLabel: UILabel!
|
|
|
|
var msgsHistoryService: CryptoChat.Service.MsgsHistory?
|
|
|
|
private let refreshControl = UIRefreshControl()
|
|
|
|
let header = CryptoChatViewChatsHeader(width: UIScreen.main.bounds.width)
|
|
var chats: Results<CryptoChatModelRealmChat>?
|
|
var msgs: Results<CryptoChatModelRealmMessage>?
|
|
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
|
private lazy var noAccountsView: CommonViewEmpty = {
|
|
CommonViewEmpty(
|
|
title: L10n.CryptoChat.Chats.NoAccounts.title,
|
|
text: L10n.Account.Empty.text,
|
|
image: Asset.accountEmpty.image,
|
|
backgroundColor: Asset.snow.color,
|
|
submit: L10n.Account.Empty.submit
|
|
) { [weak self] in
|
|
guard let self = self else { return }
|
|
AccountController.showPopup(in: self)
|
|
}
|
|
}()
|
|
|
|
private lazy var emptyView: CommonViewEmpty = {
|
|
CommonViewEmpty(
|
|
title: L10n.CryptoChat.Chats.Empty.title,
|
|
text: L10n.CryptoChat.Chats.Empty.text,
|
|
image: Asset.chatsEmpty.image,
|
|
submit: L10n.CryptoChat.Chats.Empty.submit,
|
|
submitRealtion: .top
|
|
) { [weak self] in
|
|
guard let self = self else { return }
|
|
self.showSelectChatAddMethodPopup()
|
|
}
|
|
}()
|
|
|
|
private lazy var encodedView: CommonViewEmpty = {
|
|
CommonViewEmpty(
|
|
title: L10n.CryptoChat.Chats.Encoded.title,
|
|
text: L10n.CryptoChat.Chats.encoded,
|
|
image: Asset.chatsLocked.image,
|
|
submit: L10n.CryptoChat.Chats.encodedButton,
|
|
submitRealtion: .top
|
|
) { [weak self] in
|
|
guard let self = self else { return }
|
|
self.onChange(wallet: Accounts().current)
|
|
}
|
|
}()
|
|
|
|
var bottomSpace: CGFloat = 0
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
navigationController?.interactivePopGestureRecognizer?.delegate = nil
|
|
|
|
self.navigationItem.title = L10n.CryptoChat.Chats.title
|
|
|
|
tableView.showsVerticalScrollIndicator = false
|
|
tableView.separatorStyle = .none
|
|
(tableView as UIScrollView).delegate = self
|
|
|
|
tableView.register(cell: CryptoChatCellChat.self)
|
|
|
|
refreshControl.addTarget(self, action: #selector(self.refresh(_:)), for: .valueChanged)
|
|
tableView.refreshControl = refreshControl
|
|
|
|
Notification.subscribe(name: .didUpdateHistory, {
|
|
guard Accounts().current?.name == $0.userInfo?["username"] as? String else { return }
|
|
self.msgsHistoryService?.fetchFromLocalHistory()
|
|
})
|
|
|
|
self.addChatButton.addTarget(self, action: #selector(self.addButtonPressed(_:)), for: .touchUpInside)
|
|
|
|
self.setupView()
|
|
}
|
|
|
|
func setup(username: String) {
|
|
AccountViewAuthorize.showGetPrivateKey(in: self) { [weak self] privateKey in
|
|
|
|
guard let self else { return }
|
|
|
|
self.msgsHistoryService = try? CryptoChat.Service.MsgsHistory(username: username, encryptionKey: privateKey)
|
|
self.msgsHistoryService?.updatePublisher
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] _ in
|
|
self?.refreshControl.endRefreshing()
|
|
self?.tableView.reloadData()
|
|
}
|
|
.store(in: &self.cancellables)
|
|
self.chats = self.msgsHistoryService?.chats
|
|
self.msgs = self.msgsHistoryService?.filterMsgs(query: "")
|
|
|
|
self.header.searchTextField.text = ""
|
|
self.tableView.reloadData()
|
|
Accounts().messageShelf.activeBook?.fetch()
|
|
}
|
|
}
|
|
|
|
@objc
|
|
func addButtonPressed(_ sender: Any) {
|
|
self.showSelectChatAddMethodPopup()
|
|
}
|
|
|
|
func onChange(wallet: WalletKit.Wallet?) {
|
|
|
|
guard let wallet = Accounts().current else {
|
|
self.view.addSubview(self.noAccountsView)
|
|
return
|
|
}
|
|
|
|
self.msgsHistoryService = nil
|
|
self.chats = nil
|
|
self.msgs = nil
|
|
self.tableView.reloadData()
|
|
|
|
self.setup(username: wallet.name)
|
|
|
|
self.noAccountsView.removeFromSuperview()
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
Accounts().current.isExist ? self.noAccountsView.removeFromSuperview() : self.view.addSubview(noAccountsView)
|
|
|
|
Accounts().bank.activePublisher
|
|
.receive(on: DispatchQueue.main)
|
|
.sink {
|
|
self.onChange(wallet: $0)
|
|
}
|
|
.store(in: &self.cancellables)
|
|
|
|
self.navigationController?.setNavigationBarHidden(true, animated: animated)
|
|
// IQKeyboardManager.shared.enable = false
|
|
|
|
self.refresh(self)
|
|
|
|
guard let window = UIApplication.shared.windows.first,
|
|
let controller = window.rootViewController as? MainController else { return }
|
|
|
|
if (controller.barNav.items?.count ?? 0) > 1 {
|
|
controller.barNav.popItem(animated: true)
|
|
}
|
|
}
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
|
|
self.cancellables.removeAll()
|
|
|
|
self.msgsHistoryService = nil
|
|
self.chats = nil
|
|
self.msgs = nil
|
|
self.tableView.reloadData()
|
|
|
|
self.navigationController?.setNavigationBarHidden(false, animated: animated)
|
|
// IQKeyboardManager.shared.enable = true
|
|
}
|
|
|
|
override func viewDidLayoutSubviews() {
|
|
super.viewDidLayoutSubviews()
|
|
if bottomSpace == 0 {
|
|
bottomSpace += (self.tabBarController?.tabBar.frameHeight ?? 0) /*tab bar*/
|
|
+ 40 /*button height*/ + 24 /*button top space*/
|
|
}
|
|
noAccountsView.frame = tableView.frame
|
|
emptyView.frame = tableView.frame
|
|
encodedView.frame = tableView.frame
|
|
}
|
|
|
|
private func setupView() {
|
|
self.addChatButton.layer.borderColor = Asset.deepWater.color.withAlphaComponent(0.2).cgColor
|
|
self.addChatButton.layer.borderWidth = 1.0
|
|
self.addChatButton.layer.cornerRadius = 8.0
|
|
}
|
|
|
|
@objc func refresh(_ sender: AnyObject) {
|
|
// MARK: - TO DO, look for better solution for refresh
|
|
DispatchQueue.main.async {
|
|
self.refreshControl.beginRefreshing()
|
|
}
|
|
guard let username = Accounts().current?.name else { return }
|
|
if msgsHistoryService == nil {
|
|
setup(username: username)
|
|
} else {
|
|
Accounts().messageShelf.activeBook?.fetch()
|
|
}
|
|
DispatchQueue.main.async {
|
|
self.refreshControl.endRefreshing()
|
|
}
|
|
}
|
|
|
|
func openChat(username: String, unreadCount: Int) {
|
|
guard !username.isEmpty,
|
|
let service = self.msgsHistoryService else {
|
|
return
|
|
}
|
|
|
|
let chatViewController = CryptoChatControllerChat(username: username,
|
|
unreadCount: unreadCount,
|
|
service: service)
|
|
mainController.content.push(chatViewController, animated: true)
|
|
}
|
|
|
|
var query = ""
|
|
}
|
|
|
|
extension CryptoChatControllerChats: UITableViewDelegate, UITableViewDataSource {
|
|
|
|
func numberOfSections(in tableView: UITableView) -> Int {
|
|
1 + ((msgs?.count ?? 0) > 0 ? 1 : 0)
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
|
|
if let wallet = Accounts().current {
|
|
if self.msgsHistoryService == nil {
|
|
self.emptyView.removeFromSuperview()
|
|
Loader.hide(in: tableView)
|
|
self.view.addSubview(self.encodedView)
|
|
} else {
|
|
if !UserDefaults.standard.bool(forKey: "\(wallet.name)_firstMsgsSyncDone") {
|
|
Loader.show(in: tableView)
|
|
} else {
|
|
Loader.hide(in: tableView)
|
|
|
|
(self.chats?.count ?? 0) == 0 && query.isEmpty
|
|
? self.view.addSubview(self.emptyView)
|
|
: self.emptyView.removeFromSuperview()
|
|
}
|
|
self.encodedView.removeFromSuperview()
|
|
}
|
|
[
|
|
self.addChatButton,
|
|
self.header.searchTextField
|
|
]
|
|
.forEach {
|
|
$0?.isHidden = (msgsHistoryService == nil)
|
|
|| !UserDefaults.standard.bool(forKey: "\(wallet.name)_firstMsgsSyncDone")
|
|
|| ((chats?.count ?? 0) == 0 && query.isEmpty)
|
|
}
|
|
|
|
} else {
|
|
Loader.hide(in: tableView)
|
|
self.emptyView.removeFromSuperview()
|
|
self.encodedView.removeFromSuperview()
|
|
}
|
|
|
|
return section == 0
|
|
? (self.chats?.count ?? 0)
|
|
: (self.msgs?.count ?? 0)
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let cell = tableView.dequeueReusableCell(CryptoChatCellChat.self, indexPath: indexPath)
|
|
|
|
if indexPath.section == 0 {
|
|
guard let chat = chats?[indexPath.row] else { return cell }
|
|
|
|
cell.titleLabel.text = chat.chatName
|
|
cell.titleShortLabel.text = String(chat.chatName.prefix(3))
|
|
cell.descriptionLabel.text = chat.textDescription
|
|
cell.timeLabel.text = chat.time
|
|
cell.setUnread(count: chat.unreadCount)
|
|
} else {
|
|
guard let msg = msgs?[indexPath.row] else { return cell }
|
|
|
|
cell.titleLabel.text = msg.chatName
|
|
cell.titleShortLabel.text = String(msg.chatName.prefix(3))
|
|
cell.descriptionLabel.text = msg.textDescription
|
|
cell.timeLabel.text = msg.time
|
|
cell.setUnread(count: nil)
|
|
}
|
|
|
|
return cell
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
if section == 0 {
|
|
self.header.parent = self
|
|
self.header.searchTextField.delegate = self
|
|
return header
|
|
} else {
|
|
let button = CommonButtonAction(width: UIScreen.main.bounds.width, height: 40)
|
|
button.style = .secondary
|
|
button.backgroundColor = Asset.deepWater.color.withAlphaComponent(0.1)
|
|
button.isUserInteractionEnabled = false
|
|
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 24)
|
|
button.cornerRadius = 0
|
|
button.setTitle(L10n.CryptoChat.Chats.Search.msgsSection, for: .normal)
|
|
button.setTitleColor(Asset.textDeepWater.color, for: .normal)
|
|
button.titleLabel?.font = Font.font(style: .bold, size: 12)
|
|
button.contentHorizontalAlignment = .left
|
|
return UIStackView(subviews: [UIView(height: 16, color: .white), button], axis: .vertical, distribution: .fill, alignment: .fill, spacing: 0)
|
|
}
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
|
section == 0 ? 65 : 56
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
|
|
if indexPath.section == 0 {
|
|
guard let chat = chats?[indexPath.row] else { return }
|
|
openChat(username: chat.chatName, unreadCount: chat.unreadCount)
|
|
} else {
|
|
guard let msg = msgs?[indexPath.row] else { return }
|
|
openChat(username: msg.chatName, unreadCount: 0)
|
|
}
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
|
section == 1 ? 56 : 0
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView,
|
|
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
|
|
|
if !query.isEmpty { return nil }
|
|
|
|
guard let chats = self.chats else { return nil }
|
|
|
|
let chat = chats[indexPath.row]
|
|
let contextItem = UIContextualAction(style: .destructive,
|
|
title: L10n.Common.Button.delete) { [weak self] (contextualAction, view, boolValue) in
|
|
self?.msgsHistoryService?.hideChatMsgs(chatName: chat.chatName)
|
|
}
|
|
|
|
let actions = UISwipeActionsConfiguration(actions: [contextItem])
|
|
return actions
|
|
|
|
}
|
|
|
|
}
|
|
|
|
extension CryptoChatControllerChats: CommonTextFieldSearchDelegate {
|
|
|
|
func textField(_ textField: CommonTextFieldSearch, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
if let text = textField.text {
|
|
let updatedText = (text as NSString).replacingCharacters(in: range, with: string)
|
|
if updatedText.isEmpty {
|
|
self.chats = self.msgsHistoryService?.chats
|
|
self.msgs = self.msgsHistoryService?.filterMsgs(query: "")
|
|
self.query = ""
|
|
} else {
|
|
self.chats = self.msgsHistoryService?.filterChats(query: updatedText)
|
|
self.msgs = self.msgsHistoryService?.filterMsgs(query: updatedText)
|
|
self.query = updatedText
|
|
}
|
|
} else {
|
|
self.chats = self.msgsHistoryService?.chats
|
|
self.msgs = self.msgsHistoryService?.filterMsgs(query: "")
|
|
self.query = ""
|
|
}
|
|
|
|
tableView.reloadData()
|
|
|
|
return true
|
|
}
|
|
|
|
func textFieldShouldClear(_ textField: CommonTextFieldSearch) -> Bool {
|
|
self.chats = self.msgsHistoryService?.chats
|
|
self.msgs = self.msgsHistoryService?.filterMsgs(query: "")
|
|
self.query = ""
|
|
self.tableView.reloadData()
|
|
return true
|
|
}
|
|
}
|
|
|
|
// MARK: - Opening QR to select New Chat Add Method
|
|
extension CryptoChatControllerChats: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
|
|
|
func showSelectChatAddMethodPopup() {
|
|
|
|
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
|
|
|
let openScannerAction: ((UIAlertAction) -> Void) = { [weak self] _ in
|
|
self?.openScannerQrViewController { [weak self] in
|
|
self?.dismiss(animated: true)
|
|
let username = self?.injectUsername(string: $0) ?? .init()
|
|
guard !username.isEmpty else { return }
|
|
|
|
switch username {
|
|
case let username where URL(string: username)?.absoluteString.contains("app.link") ?? false:
|
|
Branch.getInstance().application(UIApplication.shared, open: URL(string: username), options: nil)
|
|
default:
|
|
self?.validateEosAccount(username: username)
|
|
}
|
|
}
|
|
}
|
|
|
|
let cryptoChatCreateAction: ((UIAlertAction) -> Void) = { [weak self] _ in
|
|
self?.openCryptoChatCreatePopup()
|
|
}
|
|
|
|
let imagePickerAction: ((UIAlertAction) -> Void) = { [weak self] _ in
|
|
self?.openImagePickerViewController()
|
|
}
|
|
|
|
alert.addAction(.init(title: L10n.CryptoChat.Chats.Popup.findAccount, style: .default, handler: cryptoChatCreateAction))
|
|
|
|
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
|
|
alert.addAction(.init(title: L10n.CryptoChat.Chats.Popup.qrLibrary, style: .default, handler: imagePickerAction))
|
|
}
|
|
|
|
if UIImagePickerController.isSourceTypeAvailable(.camera) {
|
|
alert.addAction(.init(title: L10n.CryptoChat.Chats.Popup.qrCamera, style: .default, handler: openScannerAction))
|
|
}
|
|
|
|
alert.addAction(.init(title: L10n.Common.Button.cancel, style: .cancel, handler: nil))
|
|
self.present(alert, animated: true)
|
|
}
|
|
|
|
private func injectUsername(string: String) -> String? {
|
|
guard let stringData = string.data(using: .utf8) else { return string }
|
|
let addressDecoder = try? JSONDecoder().decode(WalletTansferQr.self, from: stringData)
|
|
|
|
return addressDecoder?.address ?? string
|
|
}
|
|
|
|
private func validateEosAccount(username: String) {
|
|
|
|
let environment = ApplicationEnvironment.shared().current
|
|
guard let node = environment.node,
|
|
let url = URL(string: node) else {
|
|
Alert.error(text: L10n.CryptoChat.Chats.createTextFieldError)
|
|
return
|
|
}
|
|
|
|
EosioRpcProvider(endpoint: url, headers: environment.headers).getAccount(requestParameters: EosioRpcAccountRequest(accountName: username), completion: { [weak self] in
|
|
switch $0 {
|
|
case .success:
|
|
self?.openChat(username: username, unreadCount: 0)
|
|
case .failure:
|
|
Alert.error(text: L10n.CryptoChat.Chats.createTextFieldError)
|
|
}
|
|
})
|
|
}
|
|
|
|
// MARK: UIImagePickerControllerDelegate & UINavigationControllerDelegate delegate methods
|
|
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) {
|
|
|
|
let qrCodeString =
|
|
(detector.features(in: ciImage) as? [CIQRCodeFeature])?
|
|
.compactMap(\.messageString)
|
|
.joined()
|
|
?? String.init()
|
|
|
|
guard let username = self.injectUsername(string: qrCodeString) else {
|
|
return
|
|
}
|
|
|
|
guard !username.isEmpty else {
|
|
Alert.error(text: L10n.CryptoChat.Chats.noQr, delay: 1)
|
|
return
|
|
}
|
|
|
|
switch username {
|
|
case let username where URL(string: username)?.absoluteString.contains("app.link") ?? false:
|
|
Branch.getInstance().application(UIApplication.shared, open: URL(string: username), options: nil)
|
|
default:
|
|
self.validateEosAccount(username: username)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Routing
|
|
extension CryptoChatControllerChats {
|
|
fileprivate func openImagePickerViewController() {
|
|
let imagePicker = UIImagePickerController()
|
|
imagePicker.delegate = self
|
|
imagePicker.sourceType = .photoLibrary
|
|
self.present(imagePicker, animated: true, completion: nil)
|
|
}
|
|
|
|
fileprivate func openCryptoChatCreatePopup() {
|
|
let view = CryptoChatViewCreateChat()
|
|
view.parent = self
|
|
Popup.show(content: CommonViewControllerViewPopUp(
|
|
title: L10n.CryptoChat.Chats.craateDescription,
|
|
image: Asset.chatCreate.image,
|
|
view: view
|
|
))
|
|
}
|
|
|
|
fileprivate func openScannerQrViewController(onAction: @escaping (String) -> Void) {
|
|
let scanner = ScannerViewController()
|
|
|
|
scanner.navigationItem.leftBarButtonItem = .close { [weak self] in
|
|
self?.dismiss(animated: true)
|
|
}
|
|
|
|
scanner.didCapture = onAction
|
|
|
|
let navCtrl = UINavigationController(rootViewController: scanner)
|
|
navCtrl.modalPresentationStyle = .fullScreen
|
|
self.navigationController?.tabBarController?.present(navCtrl, animated: true)
|
|
}
|
|
}
|