539 lines
20 KiB
Swift
539 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 WalletUIComponents
|
|
|
|
import struct RealmSwift.Results
|
|
|
|
final class CryptoChatControllerChats: UIViewController {
|
|
|
|
@IBOutlet private weak var tableView: UITableView!
|
|
@IBOutlet private weak var headerContainerView: UIView!
|
|
@IBOutlet private weak var addChatButton: UIButton!
|
|
@IBOutlet private weak var chatsListLabel: UILabel!
|
|
|
|
private var bottomSpace: CGFloat = 0
|
|
private var msgsHistoryService: CryptoChat.Service.MsgsHistory?
|
|
private weak var createChatView: CryptoChatViewCreateChat?
|
|
|
|
private var query = ""
|
|
private let refreshControl = UIRefreshControl()
|
|
|
|
private var header: CryptoChatViewChatsHeader { CryptoChatViewChatsHeader(width: UIScreen.main.bounds.width,
|
|
viewModel: self.viewModel) }
|
|
private let viewModel = TextFieldViewModel(placeholder: L10n.CryptoChat.Chats.search)
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
|
private var chats: Results<CryptoChatModelRealmChat>?
|
|
private var msgs: Results<CryptoChatModelRealmMessage>?
|
|
|
|
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)
|
|
}
|
|
}()
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
|
|
|
|
self.navigationItem.title = L10n.CryptoChat.Chats.title
|
|
|
|
self.tableView.showsVerticalScrollIndicator = false
|
|
self.tableView.separatorStyle = .none
|
|
(self.tableView as UIScrollView).delegate = self
|
|
|
|
self.tableView.register(cell: CryptoChatCellChat.self)
|
|
|
|
self.refreshControl.addTarget(self, action: #selector(self.refresh(_:)), for: .valueChanged)
|
|
self.tableView.refreshControl = self.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()
|
|
}
|
|
|
|
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.viewModel.objectWillChange
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] in
|
|
guard let self else { return }
|
|
self.textFieldDidChange(self.viewModel.text)
|
|
if self.viewModel.shouldClear { self.textFieldShouldClear() }
|
|
}
|
|
.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 self.bottomSpace == 0 {
|
|
self.bottomSpace += (self.tabBarController?.tabBar.frameHeight ?? 0) /*tab bar*/
|
|
+ 40 /*button height*/ + 24 /*button top space*/
|
|
}
|
|
self.noAccountsView.frame = self.tableView.frame
|
|
self.emptyView.frame = self.tableView.frame
|
|
self.encodedView.frame = self.tableView.frame
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private 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.viewModel.text = ""
|
|
self.tableView.reloadData()
|
|
Accounts().messageShelf.activeBook?.fetch()
|
|
}
|
|
}
|
|
|
|
private 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()
|
|
}
|
|
|
|
private func openChat(username: String, unreadCount: Int) {
|
|
guard let service = self.msgsHistoryService else { return }
|
|
|
|
let chatViewController = CryptoChatControllerChat(username: username,
|
|
unreadCount: unreadCount,
|
|
service: service)
|
|
mainController.content.push(chatViewController, animated: true)
|
|
}
|
|
|
|
private func textFieldShouldClear() {
|
|
self.chats = self.msgsHistoryService?.chats
|
|
self.msgs = self.msgsHistoryService?.filterMsgs(query: "")
|
|
self.query = ""
|
|
self.tableView.reloadData()
|
|
}
|
|
|
|
private func textFieldDidChange(_ text: String?) {
|
|
if let text {
|
|
self.chats = text.isEmpty ? self.msgsHistoryService?.chats : self.msgsHistoryService?.filterChats(query: text)
|
|
self.msgs = text.isEmpty ? self.msgsHistoryService?.filterMsgs(query: "") : self.msgsHistoryService?.filterMsgs(query: text)
|
|
self.query = text.isEmpty ? "" : text
|
|
} else {
|
|
self.chats = self.msgsHistoryService?.chats
|
|
self.msgs = self.msgsHistoryService?.filterMsgs(query: "")
|
|
self.query = ""
|
|
}
|
|
|
|
self.tableView.reloadData()
|
|
}
|
|
|
|
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
|
|
private func addButtonPressed(_ sender: Any) {
|
|
self.showSelectChatAddMethodPopup()
|
|
}
|
|
|
|
@objc
|
|
private func refresh(_ sender: AnyObject) {
|
|
// TODO: look for better solution for refresh
|
|
DispatchQueue.main.async { [weak self] in
|
|
self?.refreshControl.beginRefreshing()
|
|
}
|
|
guard let username = Accounts().current?.name else { return }
|
|
if self.msgsHistoryService == nil {
|
|
self.setup(username: username)
|
|
} else {
|
|
Accounts().messageShelf.activeBook?.fetch()
|
|
}
|
|
DispatchQueue.main.async { [weak self] in
|
|
self?.refreshControl.endRefreshing()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - UITableViewDelegate, UITableViewDataSource
|
|
|
|
extension CryptoChatControllerChats: UITableViewDelegate, UITableViewDataSource {
|
|
|
|
func numberOfSections(in tableView: UITableView) -> Int {
|
|
1 + ((self.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 && self.query.isEmpty
|
|
? self.view.addSubview(self.emptyView)
|
|
: self.emptyView.removeFromSuperview()
|
|
}
|
|
self.encodedView.removeFromSuperview()
|
|
}
|
|
[
|
|
self.addChatButton,
|
|
self.header
|
|
]
|
|
.forEach {
|
|
$0?.isHidden = (self.msgsHistoryService == nil)
|
|
|| !UserDefaults.standard.bool(forKey: "\(wallet.name)_firstMsgsSyncDone")
|
|
|| ((self.chats?.count ?? 0) == 0 && self.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 = self.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 = self.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 {
|
|
return self.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 = self.chats?[indexPath.row] else { return }
|
|
self.openChat(username: chat.chatName, unreadCount: chat.unreadCount)
|
|
} else {
|
|
guard let msg = self.msgs?[indexPath.row] else { return }
|
|
self.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 !self.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] (_, _, _) in
|
|
self?.msgsHistoryService?.hideChatMsgs(chatName: chat.chatName)
|
|
}
|
|
|
|
let actions = UISwipeActionsConfiguration(actions: [contextItem])
|
|
return actions
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
Popup.show(
|
|
content: CommonViewControllerViewPopUp(
|
|
title: L10n.CryptoChat.Chats.craateDescription,
|
|
image: Asset.chatCreate.image,
|
|
view: view
|
|
), in: self
|
|
)
|
|
|
|
view.succeedPublisher
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] username in
|
|
guard let self else { return }
|
|
Popup.hide(in: self) { [weak self] in
|
|
self?.openChat(username: username, unreadCount: 0)
|
|
}
|
|
}
|
|
.store(in: &self.cancellables)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|