Manage macOS windows for multiple simultaneous requests

This commit is contained in:
Ivan Grachev 2022-08-01 16:39:07 +03:00
parent 6ab514190d
commit 6c7744499d
8 changed files with 102 additions and 158 deletions

View File

@ -54,7 +54,7 @@ class Agent: NSObject {
self?.hasPassword = true
self?.showInitialScreen(externalRequest: externalRequest)
}
let windowController = Window.showNew()
let windowController = Window.showNew(closeOthers: true)
windowController.contentViewController = welcomeViewController
return
}
@ -76,22 +76,22 @@ class Agent: NSObject {
if case let .safari(request) = request {
processSafariRequest(request)
} else {
let windowController = Window.showNew()
let accountsList = instantiate(AccountsListViewController.self)
if case let .wcSession(session) = request {
accountsList.onSelectedWallet = onSelectedWallet(session: session)
}
let windowController = Window.showNew(closeOthers: accountsList.onSelectedWallet == nil)
windowController.contentViewController = accountsList
}
}
func showApprove(transaction: Transaction, chain: EthereumChain, peerMeta: PeerMeta?, completion: @escaping (Transaction?) -> Void) {
let windowController = Window.showNew()
let approveViewController = ApproveTransactionViewController.with(transaction: transaction, chain: chain, peerMeta: peerMeta) { [weak self] transaction in
func showApprove(windowController: NSWindowController, transaction: Transaction, chain: EthereumChain, peerMeta: PeerMeta?, completion: @escaping (Transaction?) -> Void) {
let window = windowController.window
let approveViewController = ApproveTransactionViewController.with(transaction: transaction, chain: chain, peerMeta: peerMeta) { [weak self, weak window] transaction in
if transaction != nil {
self?.askAuthentication(on: windowController.window, onStart: false, reason: .sendTransaction) { success in
self?.askAuthentication(on: window, onStart: false, reason: .sendTransaction) { success in
completion(success ? transaction : nil)
}
} else {
@ -101,13 +101,13 @@ class Agent: NSObject {
windowController.contentViewController = approveViewController
}
func showApprove(subject: ApprovalSubject, meta: String, peerMeta: PeerMeta?, completion: @escaping (Bool) -> Void) {
let windowController = Window.showNew()
let approveViewController = ApproveViewController.with(subject: subject, meta: meta, peerMeta: peerMeta) { [weak self] result in
func showApprove(windowController: NSWindowController, subject: ApprovalSubject, meta: String, peerMeta: PeerMeta?, completion: @escaping (Bool) -> Void) {
let window = windowController.window
let approveViewController = ApproveViewController.with(subject: subject, meta: meta, peerMeta: peerMeta) { [weak self, weak window] result in
if result {
self?.askAuthentication(on: windowController.window, onStart: false, reason: subject.asAuthenticationReason) { success in
self?.askAuthentication(on: window, getBackTo: window?.contentViewController, onStart: false, reason: subject.asAuthenticationReason) { success in
completion(success)
(windowController.contentViewController as? ApproveViewController)?.enableWaiting()
(window?.contentViewController as? ApproveViewController)?.enableWaiting()
}
} else {
completion(result)
@ -116,11 +116,6 @@ class Agent: NSObject {
windowController.contentViewController = approveViewController
}
func showErrorMessage(_ message: String) {
let windowController = Window.showNew()
windowController.contentViewController = ErrorViewController.withMessage(message)
}
func getWalletSelectionCompletionIfShouldSelect() -> ((EthereumChain?, TokenaryWallet?, Account?) -> Void)? {
let session = getSessionFromPasteboard()
return onSelectedWallet(session: session)
@ -221,7 +216,7 @@ class Agent: NSObject {
guard let session = session else { return nil }
return { [weak self] chain, wallet, account in
guard let chain = chain, let wallet = wallet, account?.coin == .ethereum else {
Window.closeAllAndActivateBrowser(force: nil)
Window.closeAllAndActivateBrowser(specific: nil)
return
}
self?.connectWallet(session: session, chainId: chain.id, wallet: wallet)
@ -257,12 +252,12 @@ class Agent: NSObject {
let canDoLocalAuthentication = context.canEvaluatePolicy(policy, error: &error)
func showPasswordScreen() {
let window = on ?? Window.showNew().window
let window = on ?? Window.showNew(closeOthers: onStart).window
let passwordViewController = PasswordViewController.with(mode: .enter, reason: reason) { [weak window] success in
if let getBackTo = getBackTo {
window?.contentViewController = getBackTo
} else {
Window.closeAll()
Window.closeWindowAndActivateNext(idToClose: window?.windowNumber, specificBrowser: nil)
}
completion(success)
}
@ -287,36 +282,50 @@ class Agent: NSObject {
}
private func connectWallet(session: WCSession, chainId: Int, wallet: TokenaryWallet) {
let windowController = Window.showNew()
let windowController = Window.showNew(closeOthers: true)
let window = windowController.window
windowController.contentViewController = WaitingViewController.withReason(Strings.connecting)
walletConnect.connect(session: session, chainId: chainId, walletId: wallet.id) { [weak window] _ in
if window?.isVisible == true {
Window.closeAllAndActivateBrowser(force: nil)
Window.closeAllAndActivateBrowser(specific: nil)
}
}
}
private func processSafariRequest(_ safariRequest: SafariRequest) {
var windowNumber: Int?
let action = DappRequestProcessor.processSafariRequest(safariRequest) {
Window.closeAllAndActivateBrowser(force: .safari)
Window.closeWindowAndActivateNext(idToClose: windowNumber, specificBrowser: .safari)
}
switch action {
case .none:
break
case .selectAccount(let action), .switchAccount(let action):
let windowController = Window.showNew()
case .selectAccount(let accountAction), .switchAccount(let accountAction):
let closeOtherWindows: Bool
if case .selectAccount = action {
closeOtherWindows = false
} else {
closeOtherWindows = true
}
let windowController = Window.showNew(closeOthers: closeOtherWindows)
windowNumber = windowController.window?.windowNumber
let accountsList = instantiate(AccountsListViewController.self)
accountsList.onSelectedWallet = action.completion
accountsList.onSelectedWallet = accountAction.completion
windowController.contentViewController = accountsList
case .approveMessage(let action):
showApprove(subject: action.subject, meta: action.meta, peerMeta: action.peerMeta, completion: action.completion)
let windowController = Window.showNew(closeOthers: false)
windowNumber = windowController.window?.windowNumber
showApprove(windowController: windowController, subject: action.subject, meta: action.meta, peerMeta: action.peerMeta, completion: action.completion)
case .approveTransaction(let action):
showApprove(transaction: action.transaction, chain: action.chain, peerMeta: action.peerMeta, completion: action.completion)
let windowController = Window.showNew(closeOthers: false)
windowNumber = windowController.window?.windowNumber
showApprove(windowController: windowController, transaction: action.transaction, chain: action.chain, peerMeta: action.peerMeta, completion: action.completion)
case .justShowApp:
let windowController = Window.showNew()
let windowController = Window.showNew(closeOthers: true)
windowNumber = windowController.window?.windowNumber
let accountsList = instantiate(AccountsListViewController.self)
windowController.contentViewController = accountsList
}

View File

@ -679,7 +679,7 @@
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="173" y="-44"/>
<point key="canvasLocation" x="158" y="-43"/>
</scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
@ -1218,75 +1218,6 @@ DQ
</objects>
<point key="canvasLocation" x="75" y="1188"/>
</scene>
<!--Error View Controller-->
<scene sceneID="LSi-74-yAS">
<objects>
<viewController storyboardIdentifier="ErrorViewController" id="Os3-h2-6sc" customClass="ErrorViewController" customModule="Tokenary" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="cFH-jd-zLP">
<rect key="frame" x="0.0" y="0.0" width="250" height="350"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="T7q-cN-jjc">
<rect key="frame" x="14" y="292" width="222" height="34"/>
<textFieldCell key="cell" controlSize="large" alignment="center" title="Error" id="t7e-cO-oeN">
<font key="font" metaFont="systemHeavy" size="29"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="12" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="owz-dg-x2C">
<rect key="frame" x="106" y="32" width="39" height="28"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bRK-Tb-oKu">
<rect key="frame" x="-6" y="-6" width="51" height="40"/>
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="xDk-iz-zcw">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<connections>
<action selector="actionButtonTapped:" target="Os3-h2-6sc" id="1PU-4O-ljb"/>
</connections>
</buttonCell>
</button>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="M1Q-Iu-4dD">
<rect key="frame" x="34" y="175" width="182" height="25"/>
<textFieldCell key="cell" controlSize="large" selectable="YES" alignment="center" title="Failed to connect" id="qy3-O0-p56">
<font key="font" metaFont="systemBold" size="21"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="owz-dg-x2C" firstAttribute="centerX" secondItem="cFH-jd-zLP" secondAttribute="centerX" id="0OK-ZZ-1lf"/>
<constraint firstItem="T7q-cN-jjc" firstAttribute="leading" secondItem="cFH-jd-zLP" secondAttribute="leading" constant="16" id="0mu-SM-Kvn"/>
<constraint firstAttribute="trailing" secondItem="T7q-cN-jjc" secondAttribute="trailing" constant="16" id="AOT-En-wYh"/>
<constraint firstItem="M1Q-Iu-4dD" firstAttribute="trailing" secondItem="T7q-cN-jjc" secondAttribute="trailing" constant="-20" id="Gbp-eZ-HHz"/>
<constraint firstAttribute="bottom" secondItem="owz-dg-x2C" secondAttribute="bottom" constant="32" id="O7Z-WD-F1G"/>
<constraint firstItem="M1Q-Iu-4dD" firstAttribute="centerY" secondItem="cFH-jd-zLP" secondAttribute="centerY" constant="-12" id="TQG-iy-bgb"/>
<constraint firstItem="M1Q-Iu-4dD" firstAttribute="leading" secondItem="T7q-cN-jjc" secondAttribute="leading" constant="20" id="XO8-WA-SOb"/>
<constraint firstItem="T7q-cN-jjc" firstAttribute="top" secondItem="cFH-jd-zLP" secondAttribute="top" constant="24" id="qQl-EG-gwj"/>
</constraints>
</view>
<connections>
<outlet property="messageLabel" destination="M1Q-Iu-4dD" id="s4z-1r-zIH"/>
<outlet property="titleLabel" destination="T7q-cN-jjc" id="nXO-3X-Qbu"/>
</connections>
</viewController>
<customObject id="gpa-4h-EjA" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="380" y="1177"/>
</scene>
<!--Password View Controller-->
<scene sceneID="Riu-mP-tQs">
<objects>
@ -1671,7 +1602,7 @@ DQ
</viewController>
<customObject id="E6z-rT-Kks" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-219" y="1197"/>
<point key="canvasLocation" x="380" y="1197"/>
</scene>
<!--Edit Accounts View Controller-->
<scene sceneID="PtW-Bw-c7x">

View File

@ -1,27 +0,0 @@
// Copyright © 2021 Tokenary. All rights reserved.
import Cocoa
class ErrorViewController: NSViewController {
static func withMessage(_ message: String) -> ErrorViewController {
let new = instantiate(ErrorViewController.self)
new.message = message
return new
}
private var message = ""
@IBOutlet weak var titleLabel: NSTextField!
@IBOutlet weak var messageLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
messageLabel.stringValue = message
}
@IBAction func actionButtonTapped(_ sender: Any) {
Window.closeAllAndActivateBrowser(force: nil)
}
}

View File

@ -21,6 +21,7 @@ class PasswordViewController: NSViewController {
private var reason: AuthenticationReason?
private var passwordToRepeat: String?
private var completion: ((Bool) -> Void)?
private var didCallCompletion = false
@IBOutlet weak var reasonLabel: NSTextField!
@IBOutlet weak var cancelButton: NSButton!
@ -43,6 +44,11 @@ class PasswordViewController: NSViewController {
}
}
override func viewDidAppear() {
super.viewDidAppear()
view.window?.delegate = self
}
func switchToMode(_ mode: Mode) {
self.mode = mode
switch mode {
@ -67,11 +73,11 @@ class PasswordViewController: NSViewController {
let repeated = passwordTextField.stringValue
if repeated == passwordToRepeat {
keychain.save(password: repeated)
completion?(true)
callCompletion(result: true)
}
case .enter:
if keychain.password == passwordTextField.stringValue {
completion?(true)
callCompletion(result: true)
}
}
}
@ -83,7 +89,14 @@ class PasswordViewController: NSViewController {
case .repeatAfterCreate:
switchToMode(.create)
case .enter:
completion?(false)
callCompletion(result: false)
}
}
private func callCompletion(result: Bool) {
if !didCallCompletion {
didCallCompletion = true
completion?(result)
}
}
@ -96,3 +109,11 @@ extension PasswordViewController: NSTextFieldDelegate {
}
}
extension PasswordViewController: NSWindowDelegate {
func windowWillClose(_ notification: Notification) {
callCompletion(result: false)
}
}

View File

@ -22,7 +22,7 @@ class WaitingViewController: NSViewController {
}
@IBAction func actionButtonTapped(_ sender: Any) {
Window.closeAll()
Window.closeWindowAndActivateNext(idToClose: view.window?.windowNumber, specificBrowser: nil)
}
}

View File

@ -133,12 +133,14 @@ class WalletConnect {
let peer = PeerMeta(wcPeerMeta: getPeerOfInteractor(interactor))
let transaction = Transaction(from: wct.from, to: to, nonce: wct.nonce, gasPrice: wct.gasPrice, gas: wct.gas, value: wct.value, data: wct.data)
agent.showApprove(transaction: transaction, chain: chain, peerMeta: peer) { [weak self, weak interactor] transaction in
let windowController = Window.showNew(closeOthers: false)
let windowNumber = windowController.window?.windowNumber
agent.showApprove(windowController: windowController, transaction: transaction, chain: chain, peerMeta: peer) { [weak self, weak interactor] transaction in
if let transaction = transaction {
self?.sendTransaction(transaction, walletId: walletId, chainId: chainId, requestId: id, interactor: interactor)
Window.closeAllAndActivateBrowser(force: nil)
Window.closeWindowAndActivateNext(idToClose: windowNumber, specificBrowser: nil)
} else {
Window.closeAllAndActivateBrowser(force: nil)
Window.closeWindowAndActivateNext(idToClose: windowNumber, specificBrowser: nil)
self?.rejectRequest(id: id, interactor: interactor, message: Strings.canceled)
}
}
@ -162,12 +164,14 @@ class WalletConnect {
}
let peer = PeerMeta(wcPeerMeta: getPeerOfInteractor(interactor))
agent.showApprove(subject: approvalSubject, meta: message ?? "", peerMeta: peer) { [weak self, weak interactor] approved in
let windowController = Window.showNew(closeOthers: false)
let windowNumber = windowController.window?.windowNumber
agent.showApprove(windowController: windowController, subject: approvalSubject, meta: message ?? "", peerMeta: peer) { [weak self, weak interactor] approved in
if approved {
self?.sign(id: id, payload: payload, walletId: walletId, interactor: interactor)
Window.closeAllAndActivateBrowser(force: nil)
Window.closeWindowAndActivateNext(idToClose: windowNumber, specificBrowser: nil)
} else {
Window.closeAllAndActivateBrowser(force: nil)
Window.closeWindowAndActivateNext(idToClose: windowNumber, specificBrowser: nil)
self?.rejectRequest(id: id, interactor: interactor, message: Strings.canceled)
}
}

View File

@ -4,8 +4,10 @@ import Cocoa
struct Window {
static func showNew() -> NSWindowController {
closeAll()
static func showNew(closeOthers: Bool) -> NSWindowController {
if closeOthers {
closeAll()
}
let windowController = new
activate(windowController)
return windowController
@ -21,19 +23,31 @@ struct Window {
window?.makeKeyAndOrderFront(nil)
}
static func closeAllAndActivateBrowser(force browser: Browser?) {
closeAll()
activateBrowser(force: browser)
}
static func closeAll(updateStatusBarItem: Bool = true) {
NSApplication.shared.windows.forEach { $0.close() }
if updateStatusBarItem {
Agent.shared.setupStatusBarItem()
static func closeWindowAndActivateNext(idToClose: Int?, specificBrowser: Browser?) {
if let id = idToClose, let windowToClose = NSApplication.shared.windows.first(where: { $0.windowNumber == id }) {
windowToClose.close()
}
if let window = NSApplication.shared.windows.last(where: { $0.windowNumber != idToClose && $0.isOnActiveSpace && $0.contentViewController != nil }) {
activateWindow(window)
} else {
activateBrowser(specific: specificBrowser)
}
}
static func activateBrowser(force browser: Browser?) {
static func closeAllAndActivateBrowser(specific browser: Browser?) {
closeAll()
activateBrowser(specific: browser)
}
// MARK: - Private
private static func closeAll() {
NSApplication.shared.windows.forEach { $0.close() }
Agent.shared.setupStatusBarItem()
}
private static func activateBrowser(specific browser: Browser?) {
if let browser = browser {
activateBrowser(browser)
return
@ -64,11 +78,7 @@ struct Window {
NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == browser.rawValue })?.activate(options: .activateIgnoringOtherApps)
}
static var current: NSWindowController? {
return NSApplication.shared.windows.first?.windowController
}
static var new: NSWindowController {
private static var new: NSWindowController {
return NSStoryboard.main.instantiateController(withIdentifier: "initial") as! NSWindowController
}

View File

@ -123,7 +123,6 @@
2C8A09D726751A0C00993638 /* WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09D626751A0C00993638 /* WalletConnect.swift */; };
2C8A09DF267579EA00993638 /* AccountsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09DE267579EA00993638 /* AccountsListViewController.swift */; };
2C8A09E326757FC000993638 /* AccountCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09E226757FC000993638 /* AccountCellView.swift */; };
2C8A09E82675960D00993638 /* ErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09E72675960D00993638 /* ErrorViewController.swift */; };
2C8A09EB2675964700993638 /* ApproveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09EA2675964700993638 /* ApproveViewController.swift */; };
2C8A09EE2675965F00993638 /* WaitingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09ED2675965F00993638 /* WaitingViewController.swift */; };
2C8E47A326A322E8007B8354 /* RightClickTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8E47A226A322E8007B8354 /* RightClickTableView.swift */; };
@ -352,7 +351,6 @@
2C8A09D626751A0C00993638 /* WalletConnect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnect.swift; sourceTree = "<group>"; };
2C8A09DE267579EA00993638 /* AccountsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsListViewController.swift; sourceTree = "<group>"; };
2C8A09E226757FC000993638 /* AccountCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCellView.swift; sourceTree = "<group>"; };
2C8A09E72675960D00993638 /* ErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorViewController.swift; sourceTree = "<group>"; };
2C8A09EA2675964700993638 /* ApproveViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApproveViewController.swift; sourceTree = "<group>"; };
2C8A09ED2675965F00993638 /* WaitingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitingViewController.swift; sourceTree = "<group>"; };
2C8E47A226A322E8007B8354 /* RightClickTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RightClickTableView.swift; sourceTree = "<group>"; };
@ -678,7 +676,6 @@
2C1995412674C4B900A8E370 /* ImportViewController.swift */,
2C797E7D267BB88800F2CE2D /* WelcomeViewController.swift */,
2CDAB3712675B3F0009F8B97 /* PasswordViewController.swift */,
2C8A09E72675960D00993638 /* ErrorViewController.swift */,
2C901C462689E6D400D0926A /* ApproveTransactionViewController.swift */,
2C8A09EA2675964700993638 /* ApproveViewController.swift */,
2C8A09ED2675965F00993638 /* WaitingViewController.swift */,
@ -1380,7 +1377,6 @@
2C4768A9282598C5005E8D4D /* CoinDerivationCellView.swift in Sources */,
2CB4031D281D745D00BAEBEE /* NSTableView.swift in Sources */,
2CD0B3F726AC619900488D92 /* AddAccountOptionCellView.swift in Sources */,
2C8A09E82675960D00993638 /* ErrorViewController.swift in Sources */,
0D059AD226C2796200EE3023 /* ApprovalSubject.swift in Sources */,
2C264BCB27B2F2FF00234393 /* TezosSafariRequest.swift in Sources */,
2C1995422674C4B900A8E370 /* ImportViewController.swift in Sources */,