// // WalletControllerDashboard.swift // Wallet // // Created by Igor Danich on 30.08.2020. // Copyright © 2020 List. All rights reserved. // // swiftlint:disable file_length import UIKit import Combine import WalletFoundation import WalletUIComponents import struct RealmSwift.Results import class RealmSwift.NotificationToken final class P2PControllerDashboard: UIViewController { @IBOutlet var sectionLabels: [UILabel]! @IBOutlet var sectionNotificationButtons: [UIButton]! @IBOutlet var selectedSegmentPositionConsstraint: NSLayoutConstraint! @IBOutlet private weak var tokenView: P2PViewToken! @IBOutlet private weak var historyButton: UIButton! @IBOutlet private weak var cardsBtn: UIButton! @IBOutlet private weak var filtersButton: UIButton! @IBOutlet private weak var filtersBadgeView: UIView! @IBOutlet private weak var balanceLbl: UILabel! @IBOutlet private weak var topView: UIView! @IBOutlet private weak var dealsView: CommonViewEmptyContainer! @IBOutlet private weak var collectionView: UICollectionView! @IBOutlet private weak var pageControl: UIPageControl! @IBOutlet private weak var ordersView: CommonViewEmptyContainer! @IBOutlet private weak var kindCtrl: CommonSwitch! @IBOutlet var tableViewsContainerStackView: UIStackView! @IBOutlet weak var tableViewsContainerScrollView: UIScrollView! @IBOutlet var tableViews: [UITableView]! @IBOutlet private weak var offset: NSLayoutConstraint! private var emptyViews = [CommonViewEmpty(), CommonViewEmpty(), CommonViewEmpty()] private let topViewHeight: CGFloat = 365 private var didOpenChat = false private var cancellables = Set() let tablesHeaderView = P2PLockedFundsTopView() var service: P2P.Service.Dashboard! private var isRefreshing = false private let refreshs = [UIRefreshControl(), UIRefreshControl(), UIRefreshControl()] private var notificationTokens: [NotificationToken] = [] func setTabWithSroll(index: Int) { selectedSection = index var frame: CGRect = tableViewsContainerScrollView.frame frame.origin.x = frame.size.width * CGFloat(index) frame.origin.y = 0 tableViewsContainerScrollView.scrollRectToVisible(frame, animated: true) } func setToken(token: Network.Model.Token?) { if let token { service.token = token update() } } private var selectedSection: Int = 1 { didSet { self.tableViews.forEach { if let cell = $0.cellForRow(at: IndexPath(row: 0, section: 0)) as? P2pNumberFilterTableViewCell, $0.tag == oldValue { cell.searchTextField?.viewModel.isFirstResponder = false } } UIView.animate(withDuration: 0.3) { self.selectedSegmentPositionConsstraint.constant = CGFloat(self.selectedSection) * UIScreen.main.bounds.width / 3.0 self.view.layoutIfNeeded() } for (index, label) in sectionLabels.enumerated() { label.font = index == self.selectedSection ? FontFamily.GolosUI.medium.font(size: 14) : FontFamily.GolosUI.regular.font(size: 14) label.textColor = index == self.selectedSection ? Asset.textDeepWater.color : Asset.textGranite.color } for (index, button) in sectionNotificationButtons.enumerated() { if index == 0 { button.backgroundColor = Asset.brick.color } else { button.backgroundColor = index == self.selectedSection ? Asset.deepWater.color : Asset.pebble.color } } service.isMine = selectedSection == 1 update() } } @IBAction func sectionPressed(_ sender: Any) { setTabWithSroll(index: (sender as? UIButton)?.tag ?? 0) } override func viewDidLoad() { super.viewDidLoad() collectionView.register(cell: P2PCellDeal.self) refreshs.forEach { $0.addTarget(self, action: #selector(self.onRefresh), for: .valueChanged) } tableViews.forEach({ $0.register(cell: P2pNumberFilterTableViewCell.self) $0.register(cell: P2PCellOrder.self) $0.register(cell: P2PDealTableViewCell.self) $0.backgroundColor = Asset.gypsum.color $0.tableHeaderView = nil if #available(iOS 15.0, *) { $0.sectionHeaderTopPadding = 0.0 } $0.refreshControl = refreshs[$0.tag] }) tablesHeaderView.pressAction = { [weak self] in self?.onWidthdraw(nil) } tablesHeaderView.switchAction = { [weak self] in Loader.show() self?.service.dealsService.runtimeConfiguration.dealsActiveOnly = !$0 self?.service.dealsService.fetch(username: Accounts().current?.name ?? "") { Loader.hide() Alert.notify($0) } } tableViewsContainerStackView.insertArrangedSubview(tablesHeaderView, at: 0) filtersBadgeView.layer.cornerRadius = 4.0 for button in sectionNotificationButtons { button.layer.cornerRadius = 6.0 button.contentEdgeInsets = UIEdgeInsets(top: 2, left: 5, bottom: 3, right: 5) } openRightSection() self.setupEmptyView() kindCtrl.lzTextBack = L10n.P2p.Dashboard.Menu.all kindCtrl.lzTextBackSelected = L10n.P2p.Dashboard.Menu.mine kindCtrl.lzTextFront = L10n.P2p.Dashboard.Menu.mine kindCtrl.lzTextFrontSelected = L10n.P2p.Dashboard.Menu.all kindCtrl.isSelected = true offset.constant = 0 notificationTokens.append(contentsOf: [ service.ordersService.orders.observe(on: .main) { [weak self] _ in self?.update() }, service.p2pBalancesService.balances.observe(on: .main) { [weak self] _ in self?.update() }, service.dealsService.deals.observe(on: .main) { [weak self] _ in self?.update() } ]) Notification.subscribe(bag: bag, name: .didUpdateBalances) { [weak self] _ in self?.update() } Accounts().activePublisher .sink { [weak self] _ in self?.notificationTokens.removeLast() self?.notificationTokens.removeLast() self?.notificationTokens.append(contentsOf: [ self?.service.dealsService.deals.observe(on: .main) { [weak self] _ in self?.update() }, self?.service.p2pBalancesService.balances.observe(on: .main) { [weak self] _ in self?.update() } ].compactMap { $0 }) } .store(in: &self.cancellables) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationGlobalHack = self.navigationController self.navigationController?.setNavigationBarHidden(true, animated: true) if self.didOpenChat { self.mainController.barNav.popItem(animated: true) self.didOpenChat = false } self.update() // refreshAll(showAlert: false) // Fetch orders first time maybe here? but why we should fetch on each apear better sockets updates // P2P.Service.balances.fetch() // service.orders.fetch() // service.deals.fetch() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if navigationGlobalHack == self.navigationController { navigationGlobalHack = nil } } func openRightSection() { if let count = self.service.activeDeals(symbol: self.service.token?.symbol ?? ""), count > 0 { self.setTabWithSroll(index: 0) } else if service.mineOrders.count > 0 { self.setTabWithSroll(index: 1) } else { self.setTabWithSroll(index: 2) } } @objc private func onRefresh() { self.refreshAll(showAlert: true) } private func textFieldDidChange(_ text: String?) { let newString = (text ?? "").removingWhitespaceAndNewlines() if newString.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil { self.changeServiceFilteredResults(by: newString) } } private func textFieldShouldClear() { self.changeServiceFilteredResults(by: "") } private func changeServiceFilteredResults(by text: String) { switch self.selectedSection { case 0: self.service.numberFilterDeals = text case 1: self.service.numberFilterMineOrders = text case 2: self.service.numberFilter = text default: break } self.update() } private func refreshAll(showAlert: Bool) { self.isRefreshing = true Accounts().messageShelf.activeBook?.fetch() Wallet.Service.Tokens.shared.balances.fetch() let name = Accounts().current?.name ?? "" let dispatchGroup = DispatchGroup() var error: Error? dispatchGroup.enter() self.service.p2pBalancesService.fetch(username: name, completion: { if let closureError = $0 { error = closureError } dispatchGroup.leave() }) dispatchGroup.enter() self.service.ordersService.fetch(username: name, completion: { if let closureError = $0 { error = closureError } dispatchGroup.leave() }) dispatchGroup.enter() self.service.dealsService.fetch(username: name, completion: { if let closureError = $0 { error = closureError } dispatchGroup.leave() }) dispatchGroup.notify(queue: DispatchQueue.main) { self.isRefreshing = false self.endRefreshing() if showAlert { Alert.notify(error) } } self.service.p2pListsService.fetch(completion: { _ in }) } var mineOrders: Results! var notMineOrders: Results! private func update() { Loader.hide() // MARK: - TO DO, look for better solution for refresh self.startRefreshing() // Locked Funds and Switch old deals view section let blockedBalance = self.service.currentBalance?.blockedAmount.decimalValue ?? 0 let freeAmount = self.service.currentBalance?.amount.decimalValue ?? 0 self.tablesHeaderView.amountLabel.lzText = (blockedBalance + freeAmount) .toReadebleString(precisionMax: self.service.token?.precision ?? 0, symbol: self.service.token?.symbol ?? "", group: true) self.tablesHeaderView.dealsSwitch.isOn = !self.service.dealsService.runtimeConfiguration.dealsActiveOnly self.tablesHeaderView.backgroundContainerView.isHidden = (blockedBalance + freeAmount) == 0 self.tablesHeaderView.switchSection.isHidden = self.selectedSection != 0 self.tablesHeaderView.isHidden = self.tablesHeaderView.backgroundContainerView.isHidden && self.tablesHeaderView.switchSection.isHidden // End if let sectionNotificationButtons { if let count = service.activeDeals(symbol: self.service.token?.symbol ?? ""), count > 0 { self.sectionNotificationButtons[0].isHidden = false self.sectionNotificationButtons[0].lzTitle = "\(count)" } else { self.sectionNotificationButtons[0].isHidden = true } if self.service.mineOrders.count > 0 { self.sectionNotificationButtons[1].isHidden = false self.sectionNotificationButtons[1].lzTitle = "\(self.service.mineOrders.count)" } else { self.sectionNotificationButtons[1].isHidden = true } self.sectionNotificationButtons[2].isHidden = true } self.filtersBadgeView?.isHidden = self.service.isNoFiltersApplied if let kindCtrl = self.kindCtrl, !kindCtrl.isSelected { self.ordersView?.image = self.service.kind == .sell ? Asset.p2pOrdersEmptySell.image : Asset.p2pOrdersEmptyBuy.image self.ordersView?.title = L10n.P2p.Dashboard.Orders.Empty.title self.ordersView?.text = self.service.kind == .sell ? L10n.P2p.Dashboard.Orders.Empty.Sell.mine : L10n.P2p.Dashboard.Orders.Empty.Buy.mine self.emptyViews.forEach { $0.image = self.service.kind == .sell ? Asset.p2pOrdersEmptySell.image : Asset.p2pOrdersEmptyBuy.image $0.title = L10n.P2p.Dashboard.Orders.Empty.title $0.text = self.service.kind == .sell ? L10n.P2p.Dashboard.Orders.Empty.Sell.mine : L10n.P2p.Dashboard.Orders.Empty.Buy.mine } } else { self.ordersView?.image = service.kind == .sell ? Asset.p2pOrdersEmptyBuy.image : Asset.p2pOrdersEmptySell.image self.ordersView?.title = L10n.P2p.Dashboard.Orders.Empty.title self.ordersView?.text = service.kind == .sell ? L10n.P2p.Dashboard.Orders.Empty.Sell.all : L10n.P2p.Dashboard.Orders.Empty.Buy.all emptyViews.forEach { $0.image = service.kind == .sell ? Asset.p2pOrdersEmptyBuy.image : Asset.p2pOrdersEmptySell.image $0.title = L10n.P2p.Dashboard.Orders.Empty.title $0.text = service.kind == .sell ? L10n.P2p.Dashboard.Orders.Empty.Sell.all : L10n.P2p.Dashboard.Orders.Empty.Buy.all } } if service.dealsToShow.count == 0 { historyButton?.setImage(Asset.p2pHistoryEmpty.image, for: .normal) } else { if service.dealsService.runtimeConfiguration.dealsActiveOnly { historyButton?.setImage(Asset.p2pHistoryOld.image, for: .normal) } else { historyButton?.setImage(Asset.p2pHistoryActive.image, for: .normal) } } let collection = P2P.Service.tokens.map({ token -> Common.Model.Menu in var menu = token.toMenu() if let count = service.activeDeals(symbol: token.symbol) { menu.badge = ("\(count)", Asset.brick.color) } // menu.text = menu.text?.components(separatedBy: " ").last ?? "" return menu }) let selectedMenu = service.token.map({ token -> Common.Model.Menu in var menu = token.toMenu() if let count = service.activeDeals(symbol: token.symbol) { menu.badge = ("\(count)", Asset.brick.color) } if !Locale.preferredLanguages[0].lowercased().starts(with: "en") { menu.text = menu.text?.components(separatedBy: " ").last ?? "" } return menu }) tokenView?.action = .cards(collection: collection, selectedMenu: selectedMenu) { [weak self] menu in self?.service.token = P2P.Service.tokens.object(menu: menu) ((self?.mainController.content.viewControllers?[2] as? UINavigationController)?.viewControllers.first as? P2PControllerDashboard)?.service.token = P2P.Service.tokens.object(menu: menu) ((self?.mainController.content.viewControllers?[3] as? UINavigationController)?.viewControllers.first as? P2PControllerDashboard)?.service.token = P2P.Service.tokens.object(menu: menu) self?.update() } balanceLbl?.attributedText = service.balance.attributed(style: .bold, size: 16, color: Asset.textCoal.color) // dealsView.isLoading = service.dealsToShow.isEmpty ? service.deals.isLoading : false pageControl?.numberOfPages = service.dealsToShow.count if service.dealsToShow.isEmpty { // , !service.deals.isLoading { offset?.constant = 0 UIView.animate(withDuration: Animation.medium) { self.view.layoutIfNeeded() } } else if let offset, offset.constant == 0 { offset.constant = topViewHeight UIView.animate(withDuration: Animation.medium) { self.view.layoutIfNeeded() } } // collectionView.reloadData() // ordersView.isEmpty = (service.orders.isEmpty && selectedSection != 0) // || (selectedSection == 0 && service.dealsToShow.isEmpty) guard !self.isRefreshing else { return } self.endRefreshing() } private func startRefreshing() { DispatchQueue.main.async { [weak self] in self?.refreshs.forEach { $0.beginRefreshing() } } } private func endRefreshing() { DispatchQueue.main.async { [weak self] in self?.refreshs.forEach { $0.endRefreshing() } self?.tableViews.forEach { $0.reloadSections([1], with: .automatic) } } } private func setupEmptyView() { self.tableViews.forEach { guard let view = self.emptyViews[safe: $0.tag] else { return } view.isHidden = true view.frame = CGRect(x: 0, y: 50, width: $0.frame.width, height: $0.frame.height / 1.5) $0.addSubview(view) } } @IBAction func onChange(_: AnyObject?) { self.service.isMine = !kindCtrl.isSelected self.update() } @IBAction func onWidthdraw(_: AnyObject?) { let symbol = (self.service.token?.symbol ?? "") let blockedBalance = (self.service.currentBalance?.blockedAmount.decimalValue.toString(max: 5) ?? "0") let amount = (self.service.currentBalance?.amount.decimalValue.toString(max: 5) ?? "0") self.showBalances(blocked: [blockedBalance, symbol].joined(separator: " "), amount: [amount, symbol].joined(separator: " ")) } @IBAction func onHistory(_: AnyObject?) { // service.isHistoryDeals = !service.isHistoryDeals self.collectionView.reloadData() self.update() } @IBAction func onCards(_: AnyObject?) { self.mainController.content.push(StoryboardScene.Cards.initial.instantiate(), animated: true) } @IBAction func onFilters(_: AnyObject?) { guard let navigationController = self.navigationController else { return } let vc = P2PFilterViewController() vc.selectedToken = self.service.token let nc = UINavigationController(rootViewController: vc) vc.service = self.service.p2pListsService vc.selectedFilterValues = self.service.filters // vc.clearButtonAction = { [weak self] in // self?.service.filters = FilterSelectedValues() // self?.update() // } vc.applyButtonAction = { [weak self] in self?.service.filters = $0 self?.update() nc.dismiss(animated: true, completion: nil) } nc.modalPresentationStyle = .overFullScreen navigationController.present(nc, animated: true) } @IBAction func onExpand(_: AnyObject?) { self.offset.constant = topViewHeight UIView.animate(withDuration: Animation.medium) { self.view.layoutIfNeeded() } } @IBAction func onCreate(_: AnyObject?) { Popup.show(content: OrderTypeSelectionPopupViewController(kind: service.kind) { [weak service] in guard let service = service else { return } print($0) let ctrl = StoryboardScene.P2P.order.instantiate() ctrl.service = .init(kind: service.kind, contentType: $0, token: service.token) self.mainController.content.push(ctrl, animated: true) }, contentScrollIsEnabled: false) } private var panPos: CGPoint! @IBAction func onPan(_ pan: UIPanGestureRecognizer) { guard let panView = pan.view, service.dealsToShow.count > 0 else { return } switch pan.state { case .began: panPos = pan.location(in: panView) case .changed: let value = pan.location(in: panView).y - panPos.y if offset.constant + value < 60 { offset.constant = 60 } else if offset.constant + value > topViewHeight { offset.constant = topViewHeight } else { offset.constant += value } view.layoutIfNeeded() panPos = pan.location(in: panView) case .ended, .failed, .cancelled: panPos = nil default: break } } func edit(order: P2POrder) { let ctrl = StoryboardScene.P2P.order.instantiate() ctrl.service = .init(kind: order.kind, contentType: order.contentType, token: service.token, order: P2POrder(value: order)) ctrl.service.card = Cards.shared.collection .first(where: { $0.number == order.cardNumber ?? "" }) ?? Cards.shared.default ctrl.service.paymentSystem = order.paymentSystem.map({ (id: $0.id, title: $0.name, description: "") }) ctrl.service.tokenGet = order.currency.map({ (id: $0.id, title: $0.name, description: $0.descriptn ?? "") }) ctrl.service.blockchain = order.blockchain.map({ (id: $0.id, title: $0.name, description: $0.descriptn ?? "") }) ctrl.service.rateToken1 = order.rateIsReversed ? order.rate.decimalValue.toReadebleString(precisionMax: 12) : "1" ctrl.service.rateToken2 = order.rateIsReversed ? "1" : order.rate.decimalValue.toReadebleString(precisionMax: 12) ctrl.service.amount = order.volume.decimalValue.toReadebleString(precisionMax: 2) ctrl.service.limitMin = order.min.decimalValue.toReadebleString(precisionMax: 2) ctrl.service.limitMax = order.max.decimalValue.toReadebleString(precisionMax: 2) ctrl.service.link = order.paymentUrl ?? "" ctrl.service.vcardNumber = order.cardNumber ?? "" ctrl.service.vcardName = order.cardName ?? "" ctrl.service.vcardExpiration = order.cardDate ?? "" ctrl.service.vcardCVV = order.cvv ?? "" ctrl.service.country = order.country.map({ (id: $0.id, title: $0.name, description: "") }) ctrl.service.city = order.city.map({ (id: $0.id, title: $0.name, description: "") }) ctrl.service.bank = order.banks.map({ (id: $0.id, title: $0.name, description: "") }) ctrl.service.account = order.walletNumber ?? order.walletAddress ?? "" ctrl.service.memo = order.memo ?? "" self.mainController.content.push(ctrl, animated: true) } } extension P2PControllerDashboard: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { // print("dfggerg") // guard service.dealsToShow.count > 0 else { return } // if scrollView == tableView { // let value = offset.constant - scrollView.contentOffset.y // if value > 60 && value < 247 { // scrollView.contentOffset = .zero // offset.constant = value // topView.alpha = 0 // } else if value <= 60 { // offset.constant = 60 // topView.alpha = 1 // } else if value >= topViewHeight { // offset.constant = topViewHeight // topView.alpha = 0 // } // view.layoutIfNeeded() // } else if scrollView == collectionView { // pageControl.currentPage = Int( // CGFloat(pageControl.numberOfPages)*(scrollView.contentOffset.x + scrollView.frameWidth/2)/scrollView.contentSize.width // ) // } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { print(scrollView.contentOffset.x / scrollView.frame.size.width) if scrollView == tableViewsContainerScrollView { selectedSection = Int(scrollView.contentOffset.x / scrollView.frame.size.width) } } } extension P2PControllerDashboard: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { return UICollectionViewCell() } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 0 // service.dealsToShow.count } // swiftlint:disable:next function_body_length func tableViewDealCell(_ tableView: UITableView, dealCellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(P2PDealTableViewCell.self, indexPath: indexPath) guard indexPath.row < service.dealsToShow.count else { return cell } cell.p2pDealView.tableView = tableView let deal = service.dealsToShow[indexPath.row] cell.p2pDealView.setup(model: deal) { [weak self] action in guard let self else { return } switch action { case .complete: if deal.isExpired && deal.seller == Accounts().current?.name && deal.status != .dispute { AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show() self.service.dealsActionsService.complete(privateKey: $0, deal: deal) { (error) in Loader.hide() Alert.notify(error) } } } else if deal.isExpired && deal.buyer == Accounts().current?.name && deal.status != .dispute { Popup.show( title: L10n.P2p.Dashboard.Deal.Confirm.title, views: [ .label( text: L10n.P2p.Dashboard.Deal.Confirm.Complete.Text.expired .attributed(style: .regular, size: 14, color: Asset.textCoal.color), numberOfLines: 0 ), .warning(text: L10n.P2p.Dashboard.Deal.Confirm.Complete.Warning.expired), .field( title: L10n.P2p.Dashboard.Deal.Confirm.Complete.Amount.expired, text: (deal.volume.decimalValue + deal.insurance.decimalValue).toString(max: deal.precision, symbol: deal.symbol) ), .field( title: L10n.P2p.Dashboard.Deal.Confirm.Complete.Insurance.expired, text: deal.insurance.decimalValue.toString(max: deal.precision, symbol: deal.symbol) ) ], submit: L10n.P2p.Dashboard.Deal.Confirm.submit, in: self ) { ctrl in AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show(in: ctrl) self.service.dealsActionsService.complete(privateKey: $0, deal: deal) { (error) in Loader.hide(in: ctrl) ctrl.dismiss(animated: true) Alert.notify(error) } } } } else { Popup.show( title: L10n.P2p.Dashboard.Deal.Confirm.title, views: [ .label( text: L10n.P2p.Dashboard.Deal.Confirm.Complete.text(deal.volume.decimalValue.toString(max: deal.precision, symbol: deal.symbol)) .attributed(style: .regular, size: 14, color: Asset.textCoal.color), numberOfLines: 0 ), .warning(text: L10n.P2p.Dashboard.Deal.Confirm.Complete.warning), .field( title: L10n.P2p.Dashboard.Deal.Confirm.Complete.amount, text: deal.volume.decimalValue.toString(max: deal.precision, symbol: deal.symbol) ), .field( title: L10n.P2p.Dashboard.Deal.Confirm.Complete.insurance, text: deal.insurance.decimalValue.toString(max: deal.precision, symbol: deal.symbol) ) ], submit: L10n.P2p.Dashboard.Deal.Confirm.submit, in: self ) { ctrl in AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show(in: ctrl) self.service.dealsActionsService.complete(privateKey: $0, deal: deal) { (error) in Loader.hide(in: ctrl) ctrl.dismiss(animated: true) Alert.notify(error) } } } } case .dispute: Popup.show( title: L10n.P2p.Dashboard.Deal.Confirm.title, views: [ .label( text: L10n.P2p.Dashboard.Deal.Confirm.Dispute.text1.attributed(style: .regular, size: 14, color: Asset.textCoal.color), numberOfLines: 0 ), .warning(text: L10n.P2p.Dashboard.Deal.Confirm.Dispute.warning), .label( text: L10n.P2p.Dashboard.Deal.Confirm.Dispute.text2.attributed(style: .regular, size: 14, color: Asset.textCoal.color), numberOfLines: 0 ) ], submit: L10n.P2p.Dashboard.Deal.Confirm.submit, in: self ) { ctrl in AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show(in: ctrl) self.service.dealsActionsService.dispute(privateKey: $0, deal: deal) { (error) in Loader.hide(in: ctrl) Alert.notify(error, dismiss: ctrl) } } } case .cancel: if deal.seller == Accounts().current?.name || !deal.isExpired { Popup.show( title: L10n.P2p.Dashboard.Deal.Confirm.title, views: [ .label( text: L10n.P2p.Dashboard.Deal.Confirm.Cancel.text("100").attributed(style: .regular, size: 14, color: Asset.textCoal.color), numberOfLines: 0 ), .warning(text: L10n.P2p.Dashboard.Deal.Confirm.Cancel.warning), .field( title: L10n.P2p.Dashboard.Deal.Confirm.Cancel.insurance, text: deal.insurance.decimalValue.toString(max: deal.precision, symbol: deal.symbol) ) ], submit: L10n.P2p.Dashboard.Deal.Confirm.submit, in: self ) { ctrl in AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show(in: ctrl) self.service.dealsActionsService.cancel(privateKey: $0, deal: deal) { (error) in Loader.hide(in: ctrl) Alert.notify(error, dismiss: ctrl) } } } } else { Popup.show( title: L10n.P2p.Dashboard.Deal.Confirm.title, views: [ .label( text: L10n.P2p.Dashboard.Deal.Confirm.Cancel.text("100").attributed(style: .regular, size: 14, color: Asset.textCoal.color), numberOfLines: 0 ), .warning(text: L10n.P2p.Dashboard.Deal.Confirm.Cancel.warning), .field( title: L10n.P2p.Dashboard.Deal.Confirm.Cancel.insurance, text: deal.insurance.decimalValue.toString(max: deal.precision, symbol: deal.symbol) ) ], submit: L10n.P2p.Dashboard.Deal.Confirm.submit, in: self ) { ctrl in AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show(in: ctrl) self.service.dealsActionsService.cancel(privateKey: $0, deal: deal) { (error) in Loader.hide(in: ctrl) Alert.notify(error, dismiss: ctrl) } } } } case .withdraw: AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show() self.service.dealsActionsService.withdraw(privateKey: $0, deal: deal) { (error) in Loader.hide() Alert.notify(error) } } case .pay: Loader.show() self.service.dealsActionsService.pay(deal: deal) { (error) in Loader.hide() Alert.notify(error) } case .chat: AccountViewAuthorize.showGetPrivateKey(in: self) { guard let user = Accounts().current, let service = try? CryptoChat.Service.MsgsHistory(username: user.name, encryptionKey: $0) else { return } self.didOpenChat = true let account = deal.seller == Accounts().current?.name ? deal.buyer : deal.seller let ctrl = CryptoChatControllerChat(username: account, unreadCount: 0, service: service) self.mainController.content.push(ctrl, animated: true) } } } return cell } } extension P2PControllerDashboard: UITableViewDelegate, UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { 2 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 0 { return 1 } mineOrders = service.mineOrders.freeze() notMineOrders = service.notMineOrders.freeze() var count: Int = 0 switch tableView.tag { case 0: count = service.dealsToShow.count case 1: count = mineOrders.count case 2: count = notMineOrders.count default: break } if count == 0 { emptyViews[tableView.tag].isHidden = false } else { emptyViews[tableView.tag].isHidden = true } return count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.section == 0 { let cell = tableView.dequeueReusableCell(P2pNumberFilterTableViewCell.self, indexPath: indexPath) as? P2pNumberFilterTableViewCell ?? P2pNumberFilterTableViewCell() let placeholder = tableView.tag == 0 ? L10n.P2p.Filter.Filters.searchDeals : L10n.P2p.Filter.Filters.searchOrders let viewModel = TextFieldViewModel(placeholder: placeholder, keyboardType: .numberPad) viewModel.objectWillChange .receive(on: DispatchQueue.main) .sink { [weak self, weak viewModel] in guard let self, let viewModel else { return } self.textFieldDidChange(viewModel.text) if viewModel.shouldClear { self.textFieldShouldClear() } } .store(in: &self.cancellables) cell.configureView(with: viewModel) guard let searchTextField = cell.searchTextField, !searchTextField.viewModel.isFirstResponder else { return cell } switch self.selectedSection { case 0: searchTextField.viewModel.text = service.numberFilterDeals case 1: searchTextField.viewModel.text = service.numberFilterMineOrders case 2: searchTextField.viewModel.text = service.numberFilter default: break } return cell } if tableView.tag == 0 { return tableViewDealCell(tableView, dealCellForRowAt: indexPath) } let cell = tableView.dequeueReusableCell(P2PCellOrder.self, indexPath: indexPath) let order: P2POrder switch tableView.tag { case 1: guard indexPath.row < mineOrders.count else { return cell } order = mineOrders[indexPath.row] case 2: guard indexPath.row < notMineOrders.count else { return cell } order = notMineOrders[indexPath.row] default: return cell } cell.tableView = tableView cell.setup(model: order) { [weak self] action in guard let self else { return } switch action { case .buy, .sell: let ctrl = StoryboardScene.P2P.deal.instantiate() ctrl.service = .init(order: P2POrder(value: order)) self.mainController.content.push(ctrl, animated: true) case .pause: AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show() order.pause(privateKey: $0) { (error) in Loader.hide() Alert.notify(error) } } case .resume: AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show() order.resume(privateKey: $0) { (error) in Loader.hide() Alert.notify(error) } } case .cancel: AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show() order.cancel(privateKey: $0) { (error) in Loader.hide() Alert.notify(error) } } case .chat: AccountViewAuthorize.showGetPrivateKey(in: self) { guard let user = Accounts().current, let service = try? CryptoChat.Service.MsgsHistory(username: user.name, encryptionKey: $0) else { return } self.didOpenChat = true let account = order.owner let ctrl = CryptoChatControllerChat(username: account, unreadCount: 0, service: service) self.mainController.content.push(ctrl, animated: true) } case .more: let ctrl = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) ctrl.addAction(.init(title: L10n.P2p.Dashboard.Order.Action.moreEdit, style: .default) { [weak self] _ in guard let self else { return } // if order.status == .inactive { // edit(order: order) // return // } Popup.show( title: L10n.P2p.Dashboard.Order.Action.MoreEdit.title, views: [ .label( text: (self.service.kind == .buy ? L10n.P2p.Dashboard.Order.Action.MoreEdit.descriptionBuy : L10n.P2p.Dashboard.Order.Action.MoreEdit.descriptionSell) .attributed(style: .regular, size: 14, color: Asset.textCoal.color), numberOfLines: 0 ), .warning(text: L10n.P2p.Dashboard.Order.Action.MoreEdit.warning) ], submit: order.status == .inactive ? L10n.P2p.Dashboard.Order.Action.MoreEdit.edit : L10n.P2p.Dashboard.Order.Action.MoreEdit.stopAndEdit, cancel: L10n.Common.Button.cancel, cancelStyle: .tertiary, cancelSize: .small, in: self ) { [weak self] ctrl in guard let self else { Popup.hide() return } if order.status == .active { AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show() order.pause(privateKey: $0) { (error) in Loader.hide() Popup.hide() Alert.notify(error) self.edit(order: order) } } } else { Popup.hide() self.edit(order: order) } } }) ctrl.addAction(.init(title: L10n.P2p.Dashboard.Order.Action.moreCancel, style: .default) { [weak self] _ in guard let self else { return } Popup.show( title: L10n.Common.Title.confirmation, views: [ .label( text: (self.service.kind == .buy ? L10n.P2p.Dashboard.Order.Action.MoreCancel.descriptionBuy : L10n.P2p.Dashboard.Order.Action.MoreCancel.descriptionSell) .attributed(style: .regular, size: 14, color: Asset.textCoal.color), numberOfLines: 0 ), .field( title: L10n.P2p.Dashboard.Order.Action.MoreCancel.get, text: (self.service.kind == .buy ? order.insurance.decimalValue : order.volume.decimalValue) .toReadebleString(precisionMax: order.precision, symbol: order.symbol) ), .field( title: L10n.P2p.Dashboard.Order.Action.MoreCancel.deposit, text: (order.volume.decimalValue * 0.1).toReadebleString(precisionMax: order.precision, symbol: order.symbol) ) ].dropLast(self.service.kind == .buy ? 1 : 0), submit: L10n.Common.Button.confirm, in: self ) { ctrl in AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show() order.cancel(privateKey: $0) { (error) in Loader.hide() Popup.hide() Alert.notify(error) } } } }) ctrl.addAction(.init(title: L10n.Common.Button.cancel, style: .cancel, handler: nil)) self.present(ctrl, animated: true) } } return cell } } extension P2PControllerDashboard { fileprivate func showBalances(blocked: String, amount: String) { Popup.show( title: L10n.P2p.Dashboard.Popup.Balance.title, views: [ .field(title: L10n.P2p.Dashboard.Popup.Balance.blocked, text: blocked), .field(title: L10n.P2p.Dashboard.Popup.Balance.free, text: amount) ], buttons: [ .button(title: L10n.P2p.Dashboard.Popup.Balance.Button.withdraw, isEnabled: service.withdrawBalance > 0 && service.token?.symbol != nil, action: { } ), .button(style: .tertiary, title: L10n.P2p.Dashboard.Popup.Balance.Button.showLockedFunds, size: .small, action: { }) ], buttonsActions: [ { _ in self.withrawBalance() }, { _ in Popup.hide() self.mainController.content.push(P2PLockedFundsController(), animated: true) } ], in: self ) } private func withrawBalance() { guard service.withdrawBalance > 0, let symbol = service.token?.symbol, let precision = service.token?.precision else { return } AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show() self.service.p2pBalancesService.withdraw(privateKey: $0, symbol: symbol, precision: precision) { (error) in Loader.hide() Popup.hide() Alert.notify(error, pop: self) } } } }