336 lines
13 KiB
Swift
336 lines
13 KiB
Swift
//
|
|
// AccountsListController.swift
|
|
// Malinka
|
|
//
|
|
// Created by NUT.Tech on 26.08.2022.
|
|
// Copyright © 2022 NUT.Tech. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import WalletFoundation
|
|
import WalletKit
|
|
import Combine
|
|
|
|
enum AccountStatus: Equatable {
|
|
|
|
case accepted(selected: Bool)
|
|
case creating(selected: Bool)
|
|
case pending
|
|
case declined
|
|
|
|
init(wallet: WalletKit.Wallet, selected: Bool) {
|
|
switch wallet.state {
|
|
case .accepted:
|
|
self = .accepted(selected: selected)
|
|
case .creating:
|
|
self = .creating(selected: selected)
|
|
case .pending:
|
|
self = .pending
|
|
case .declined:
|
|
self = .declined
|
|
}
|
|
}
|
|
}
|
|
|
|
final class AccountsListController: UIViewController {
|
|
|
|
private enum Constants {
|
|
enum Geometry {
|
|
static let fontSize: CGFloat = 14
|
|
static let buttonCornerRadius: CGFloat = 5
|
|
static let buttonHeight: CGFloat = 48
|
|
static let buttonMargin: CGFloat = 12
|
|
static let buttonTitlePadding: CGFloat = 5
|
|
static let stackMargin: CGFloat = 16
|
|
}
|
|
}
|
|
|
|
private var countBeforeCreated: Int?
|
|
private var store = Set<AnyCancellable>()
|
|
private lazy var scrollView: UIScrollView = {
|
|
let scrollview = UIScrollView()
|
|
scrollview.translatesAutoresizingMaskIntoConstraints = false
|
|
return scrollview
|
|
}()
|
|
|
|
private lazy var addAccountButton: UIButton = {
|
|
let button = UIButton()
|
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
button.setTitle(L10n.Main.Account.add, for: .normal)
|
|
button.setTitleColor(Asset.deepWater.color, for: .normal)
|
|
button.setImage(Asset.chatsAddPlus.image, for: .normal)
|
|
button.clipsToBounds = true
|
|
if let font = FontFamily.GolosUI.medium.font(size: Constants.Geometry.fontSize) {
|
|
button.titleLabel?.font = font
|
|
}
|
|
button.layer.cornerRadius = Constants.Geometry.buttonCornerRadius
|
|
let borderColor = Asset.deepWater.color.withAlphaComponent(0.2)
|
|
button.layer.borderColor = borderColor.cgColor
|
|
button.layer.borderWidth = 1.0
|
|
button.contentEdgeInsets = UIEdgeInsets(top: 0,
|
|
left: 0,
|
|
bottom: 0,
|
|
right: Constants.Geometry.buttonTitlePadding)
|
|
button.titleEdgeInsets = UIEdgeInsets(top: 0,
|
|
left: Constants.Geometry.buttonTitlePadding,
|
|
bottom: 0,
|
|
right: -Constants.Geometry.buttonTitlePadding)
|
|
return button
|
|
}()
|
|
|
|
private lazy var accountsStack: UIStackView = {
|
|
let stackView = UIStackView()
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
stackView.axis = .vertical
|
|
stackView.spacing = Constants.Geometry.stackMargin
|
|
return stackView
|
|
}()
|
|
|
|
private var viewModel: AccountsListViewModel?
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
self.title = L10n.Main.Account.title
|
|
self.navigationItem.leftBarButtonItem = .pop(self, { vc in vc.dismiss(animated: true) })
|
|
|
|
self.setupViewModel()
|
|
self.setupViews()
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
self.viewModel?.isLocked(true)
|
|
|
|
self.navigationController >>- {
|
|
$0.navigationBar.isTranslucent = false
|
|
$0.navigationBar.backgroundColor = Asset.snow.color
|
|
$0.setNavigationBarHidden(false, animated: true)
|
|
}
|
|
|
|
self.viewModel?.refreshAccountsStates()
|
|
}
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
self.viewModel?.isLocked(false)
|
|
}
|
|
|
|
private func setupViewModel() {
|
|
|
|
let viewModel = AccountsListViewModel()
|
|
self.viewModel = viewModel
|
|
|
|
viewModel.walletsPublisher // notifies about changes of accounts(wallets) array (number of accounts for this device)
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] wallets in
|
|
guard let self else { return }
|
|
self.accountsViewBehaviour(for: wallets)
|
|
}
|
|
.store(in: &self.store)
|
|
|
|
viewModel.updatePublisher // notifies about changes of accounts(wallets) states
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] _ in
|
|
guard let self else { return }
|
|
self.accountsViewBehaviour(for: self.viewModel?.accounts ?? [])
|
|
}
|
|
.store(in: &self.store)
|
|
|
|
viewModel.activePublisher // notifies about active account(wallet) changes
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] _ in
|
|
guard let self else { return }
|
|
if !self.removeModalRequired() {
|
|
self.refreshViews()
|
|
}
|
|
}
|
|
.store(in: &self.store)
|
|
|
|
NotificationCenter.default
|
|
.publisher(for: UIApplication.willEnterForegroundNotification)
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] _ in
|
|
guard let self else { return }
|
|
self.viewModel?.refreshAccountsStates()
|
|
}
|
|
.store(in: &self.store)
|
|
|
|
if self.viewModel?.selected == nil, let wallet = self.viewModel?.accounts.last(where: { $0.isOnActiveState }) {
|
|
self.viewModel?.activate(wallet: wallet)
|
|
}
|
|
}
|
|
|
|
private func accountsViewBehaviour(for wallets: [WalletKit.Wallet]) {
|
|
|
|
guard let countBeforeCreated = self.countBeforeCreated else {
|
|
if wallets.count == 1,
|
|
let wallet = wallets.first,
|
|
wallet.isOnActiveState {
|
|
self.viewModel?.activate(wallet: wallet)
|
|
}
|
|
self.refreshViews()
|
|
return
|
|
}
|
|
|
|
if countBeforeCreated != wallets.count,
|
|
let wallet = wallets.last(where: { $0.isOnActiveState }) {
|
|
|
|
// If previously selected exists
|
|
if let selected = self.viewModel?.selected {
|
|
|
|
if !wallet.isEquals(other: selected),
|
|
let wIndex = wallets.firstIndex(where: { $0.isEquals(other: wallet) }),
|
|
let sIndex = wallets.firstIndex(where: { $0.isEquals(other: selected) }),
|
|
wIndex > sIndex {
|
|
self.countBeforeCreated = nil
|
|
self.viewModel?.activate(wallet: wallet)
|
|
} else {
|
|
self.refreshViews()
|
|
}
|
|
|
|
} else { // No one previously selected
|
|
self.countBeforeCreated = nil
|
|
self.viewModel?.activate(wallet: wallet)
|
|
}
|
|
|
|
} else {
|
|
self.refreshViews()
|
|
}
|
|
|
|
}
|
|
|
|
private func setupViews() {
|
|
|
|
self.view.backgroundColor = Asset.snow.color
|
|
self.scrollView.refreshControl = UIRefreshControl()
|
|
self.scrollView.refreshControl?.addTarget(self, action: #selector(self.callPullToRefresh), for: .valueChanged)
|
|
self.scrollView.addSubview(self.accountsStack)
|
|
self.view.addSubview(self.scrollView)
|
|
self.view.addSubview(self.addAccountButton)
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
|
self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
|
|
self.scrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
|
|
self.scrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
|
|
|
|
self.accountsStack.topAnchor.constraint(equalTo: self.scrollView.topAnchor),
|
|
self.accountsStack.leftAnchor.constraint(equalTo: self.scrollView.leftAnchor, constant: Constants.Geometry.stackMargin),
|
|
self.accountsStack.rightAnchor.constraint(equalTo: self.scrollView.rightAnchor, constant: -Constants.Geometry.stackMargin),
|
|
self.accountsStack.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor),
|
|
self.accountsStack.widthAnchor.constraint(equalTo: self.scrollView.widthAnchor, constant: -2 * Constants.Geometry.stackMargin),
|
|
|
|
self.addAccountButton.heightAnchor.constraint(equalToConstant: Constants.Geometry.buttonHeight),
|
|
self.addAccountButton.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: Constants.Geometry.buttonMargin),
|
|
self.addAccountButton.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -Constants.Geometry.buttonMargin),
|
|
self.addAccountButton.topAnchor.constraint(equalTo: self.scrollView.bottomAnchor, constant: Constants.Geometry.buttonMargin),
|
|
self.addAccountButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -Constants.Geometry.buttonMargin)
|
|
])
|
|
|
|
self.addAccountButton.addTarget(self, action: #selector(addAccountPressed), for: .touchUpInside)
|
|
}
|
|
|
|
private func removeModalRequired() -> Bool {
|
|
if let parent = self.presentingViewController,
|
|
parent.presentedViewController == self,
|
|
self.viewModel?.isSelectedAccountAccepted ?? false {
|
|
self.dismiss(animated: true)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
private func configureView() {
|
|
|
|
self.configureLeftBarButtonItem()
|
|
self.accountsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
|
|
let accounts = self.viewModel?.accounts ?? []
|
|
accounts.forEach { account in
|
|
|
|
let selected = account.name == self.viewModel?.selected?.name
|
|
let status = AccountStatus(wallet: account, selected: selected)
|
|
|
|
let accountItem = AccountListCard(title: account.name,
|
|
keyType: account.keyType,
|
|
status: status) { [weak self] in
|
|
guard let self else { return }
|
|
AccountController.showPrivateKeyPopup(in: self, wallet: account)
|
|
} deleteAction: { [weak self] in
|
|
guard let self else { return }
|
|
Alert.system(
|
|
text: L10n.Main.Account.remove,
|
|
actions: [
|
|
.yes {
|
|
self.viewModel?.remove(wallet: account)
|
|
guard self.viewModel?.accounts.isEmpty == true else { return }
|
|
self.mainController.content.pop(animated: true)
|
|
}, .no
|
|
], in: self
|
|
)
|
|
} refreshBlock: { [weak self] in
|
|
guard let self else { return }
|
|
UIView.animate(withDuration: .slowest) {
|
|
self.accountsStack.arrangedSubviews
|
|
.compactMap({ $0 as? AccountListCard })
|
|
.forEach {
|
|
switch $0.status {
|
|
case .accepted(selected: true):
|
|
$0.status = .accepted(selected: false)
|
|
case .creating(selected: true):
|
|
$0.status = .creating(selected: false)
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
self.view.layoutIfNeeded()
|
|
self.viewModel?.activate(wallet: account)
|
|
if account.state == .accepted {
|
|
self.mainController.content.pop(animated: true)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
self.accountsStack.addArrangedSubview(accountItem)
|
|
}
|
|
|
|
self.scrollView.layoutIfNeeded()
|
|
}
|
|
|
|
private func configureLeftBarButtonItem() {
|
|
guard let leftBarButtonItem = self.navigationItem.leftBarButtonItem else { return }
|
|
|
|
let hasActiveAccounts = self.viewModel?.isSelectedAccountAccepted ?? false
|
|
if #available(iOS 16.0, *) {
|
|
leftBarButtonItem.isHidden = !hasActiveAccounts
|
|
}
|
|
leftBarButtonItem.isEnabled = hasActiveAccounts
|
|
leftBarButtonItem.image?.withTintColor(hasActiveAccounts ? Asset.dark.color : Asset.disabled.color)
|
|
}
|
|
|
|
private func refreshViews() {
|
|
self.configureView()
|
|
self.scrollView.isUserInteractionEnabled = true
|
|
self.scrollView.refreshControl?.endRefreshing()
|
|
}
|
|
|
|
@objc
|
|
private func addAccountPressed() {
|
|
AccountController.showPopup(in: self) { result in
|
|
switch result {
|
|
case .new: self.countBeforeCreated = Accounts().collection.count
|
|
default: self.countBeforeCreated = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc
|
|
private func callPullToRefresh() {
|
|
self.scrollView.isUserInteractionEnabled = false
|
|
self.viewModel?.refreshAccountsStates()
|
|
}
|
|
}
|