[AltServer] Refactors common NSMenu logic into MenuController

This commit is contained in:
Riley Testut
2021-06-04 11:53:26 -07:00
parent 52fe74fbea
commit 1616ca1c34
4 changed files with 143 additions and 48 deletions

View File

@@ -21,6 +21,8 @@ private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/alts
private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altstore.ipa")!
#endif
extension ALTDevice: MenuDisplayable {}
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@@ -41,6 +43,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private weak var authenticationAppleIDTextField: NSTextField?
private weak var authenticationPasswordTextField: NSSecureTextField?
private var connectedDevicesMenuController: MenuController<ALTDevice>!
private var sideloadIPAConnectedDevicesMenuController: MenuController<ALTDevice>!
func applicationDidFinishLaunching(_ aNotification: Notification)
{
UserDefaults.standard.registerDefaults()
@@ -56,8 +61,20 @@ class AppDelegate: NSObject, NSApplicationDelegate {
self.statusItem = item
self.appMenu.delegate = self
self.connectedDevicesMenu.delegate = self
self.sideloadIPAConnectedDevicesMenu.delegate = self
let placeholder = NSLocalizedString("No Connected Devices", comment: "")
self.connectedDevicesMenuController = MenuController<ALTDevice>(menu: self.connectedDevicesMenu, items: [])
self.connectedDevicesMenuController.placeholder = placeholder
self.connectedDevicesMenuController.action = { [weak self] device in
self?.installAltStore(to: device)
}
self.sideloadIPAConnectedDevicesMenuController = MenuController<ALTDevice>(menu: self.sideloadIPAConnectedDevicesMenu, items: [])
self.sideloadIPAConnectedDevicesMenuController.placeholder = placeholder
self.sideloadIPAConnectedDevicesMenuController.action = { [weak self] device in
self?.sideloadIPA(to: device)
}
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { (success, error) in
guard success else { return }
@@ -89,20 +106,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private extension AppDelegate
{
@objc func installAltStore(_ item: NSMenuItem)
@objc func installAltStore(to device: ALTDevice)
{
guard let index = item.menu?.index(of: item), index != -1 else { return }
let device = self.connectedDevices[index]
self.installApplication(at: altstoreAppURL, to: device)
}
@objc func sideloadIPA(_ item: NSMenuItem)
@objc func sideloadIPA(to device: ALTDevice)
{
guard let index = item.menu?.index(of: item), index != -1 else { return }
let device = self.connectedDevices[index]
let openPanel = NSOpenPanel()
openPanel.canChooseDirectories = false
openPanel.allowsMultipleSelection = false
@@ -303,6 +313,9 @@ extension AppDelegate: NSMenuDelegate
guard menu == self.appMenu else { return }
self.connectedDevices = ALTDeviceManager.shared.availableDevices
self.connectedDevicesMenuController.items = self.connectedDevices
self.sideloadIPAConnectedDevicesMenuController.items = self.connectedDevices
self.launchAtLoginMenuItem.target = self
self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:))
@@ -323,37 +336,6 @@ extension AppDelegate: NSMenuDelegate
self.installMailPluginMenuItem.target = self
self.installMailPluginMenuItem.action = #selector(AppDelegate.handleInstallMailPluginMenuItem(_:))
}
func numberOfItems(in menu: NSMenu) -> Int
{
guard menu == self.connectedDevicesMenu || menu == self.sideloadIPAConnectedDevicesMenu else { return -1 }
return self.connectedDevices.isEmpty ? 1 : self.connectedDevices.count
}
func menu(_ menu: NSMenu, update item: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool
{
guard menu == self.connectedDevicesMenu || menu == self.sideloadIPAConnectedDevicesMenu else { return false }
if self.connectedDevices.isEmpty
{
item.title = NSLocalizedString("No Connected Devices", comment: "")
item.isEnabled = false
item.target = nil
item.action = nil
}
else
{
let device = self.connectedDevices[index]
item.title = device.name
item.isEnabled = true
item.target = self
item.action = (menu == self.connectedDevicesMenu) ? #selector(AppDelegate.installAltStore(_:)) : #selector(AppDelegate.sideloadIPA(_:))
item.tag = index
}
return true
}
}
extension AppDelegate: NSTextFieldDelegate

View File

@@ -93,9 +93,6 @@
</connections>
</menuItem>
</items>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="VYb-BL-Zri"/>
</connections>
</menu>
</menuItem>
<menuItem title="Sideload .ipa" id="x0e-zI-0A2" userLabel="Install .ipa">
@@ -109,9 +106,6 @@
</connections>
</menuItem>
</items>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="N3K-su-XV6"/>
</connections>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="1ZZ-BB-xHy"/>

View File

@@ -0,0 +1,115 @@
//
// MenuController.swift
// AltServer
//
// Created by Riley Testut on 3/3/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import Foundation
import AppKit
protocol MenuDisplayable
{
var name: String { get }
}
class MenuController<T: MenuDisplayable & Hashable>: NSObject, NSMenuDelegate
{
let menu: NSMenu
var items: [T] {
didSet {
self.submenus.removeAll()
self.updateMenu()
}
}
var placeholder: String? {
didSet {
self.updateMenu()
}
}
var action: ((T) -> Void)?
var submenuHandler: ((T) -> NSMenu)?
private var submenus = [T: NSMenu]()
init(menu: NSMenu, items: [T])
{
self.menu = menu
self.items = items
super.init()
self.menu.delegate = self
}
@objc
private func performAction(_ menuItem: NSMenuItem)
{
guard case let index = self.menu.index(of: menuItem), index != -1 else { return }
let item = self.items[index]
self.action?(item)
}
@objc
func numberOfItems(in menu: NSMenu) -> Int
{
let numberOfItems = (self.items.isEmpty && self.placeholder != nil) ? 1 : self.items.count
return numberOfItems
}
@objc
func menu(_ menu: NSMenu, update menuItem: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool
{
if let text = self.placeholder, self.items.isEmpty
{
menuItem.title = text
menuItem.isEnabled = false
menuItem.target = nil
menuItem.action = nil
}
else
{
let item = self.items[index]
menuItem.title = item.name
menuItem.isEnabled = true
menuItem.target = self
menuItem.action = #selector(MenuController.performAction(_:))
menuItem.tag = index
if let submenu = self.submenus[item] ?? self.submenuHandler?(item)
{
menuItem.submenu = submenu
// Cache submenu to prevent duplicate calls to submenuHandler.
self.submenus[item] = submenu
}
}
return true
}
}
private extension MenuController
{
func updateMenu()
{
self.menu.removeAllItems()
let numberOfItems = self.numberOfItems(in: self.menu)
for index in 0 ..< numberOfItems
{
let menuItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
guard self.menu(self.menu, update: menuItem, at: index, shouldCancel: false) else { break }
self.menu.addItem(menuItem)
}
self.menu.update()
}
}