Files
SideStore/SideStoreApp/Sources/SideStoreAppKit/Settings/SettingsViewController.swift

511 lines
20 KiB
Swift
Raw Normal View History

2019-06-06 14:46:23 -07:00
//
2019-09-05 11:59:10 -07:00
// SettingsViewController.swift
2019-06-06 14:46:23 -07:00
// AltStore
//
2019-09-05 11:59:10 -07:00
// Created by Riley Testut on 8/31/19.
2019-06-06 14:46:23 -07:00
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Intents
import IntentsUI
2023-03-01 00:48:36 -05:00
import MessageUI
import SafariServices
import UIKit
2019-06-06 14:46:23 -07:00
2023-03-01 00:48:36 -05:00
import SideStoreCore
2023-03-01 00:48:36 -05:00
private extension SettingsViewController {
enum Section: Int, CaseIterable {
2019-09-05 11:59:10 -07:00
case signIn
case account
case patreon
case appRefresh
case instructions
case credits
2019-09-05 11:59:10 -07:00
case debug
}
2023-03-01 00:48:36 -05:00
enum AppRefreshRow: Int, CaseIterable {
case backgroundRefresh
2023-03-01 00:48:36 -05:00
@available(iOS 14, *)
case addToSiri
2023-03-01 00:48:36 -05:00
static var allCases: [AppRefreshRow] {
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
return [.backgroundRefresh, .addToSiri]
}
}
2023-03-01 00:48:36 -05:00
enum CreditsRow: Int, CaseIterable {
case developer
2022-04-13 20:06:57 -07:00
case operations
case designer
case softwareLicenses
}
2023-03-01 00:48:36 -05:00
enum DebugRow: Int, CaseIterable {
case sendFeedback
case refreshAttempts
case errorLog
2023-01-09 15:15:31 +08:00
case resetPairingFile
case advancedSettings
}
2019-09-05 11:59:10 -07:00
}
2019-06-06 14:46:23 -07:00
2023-03-01 00:48:36 -05:00
final class SettingsViewController: UITableViewController {
2019-09-05 11:59:10 -07:00
private var activeTeam: Team?
2023-03-01 00:48:36 -05:00
2019-09-05 11:59:10 -07:00
private var prototypeHeaderFooterView: SettingsHeaderFooterView!
2023-03-01 00:48:36 -05:00
private var debugGestureCounter = 0
private weak var debugGestureTimer: Timer?
2023-03-01 00:48:36 -05:00
2019-09-05 11:59:10 -07:00
@IBOutlet private var accountNameLabel: UILabel!
@IBOutlet private var accountEmailLabel: UILabel!
@IBOutlet private var accountTypeLabel: UILabel!
2023-03-01 00:48:36 -05:00
2019-09-05 11:59:10 -07:00
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
2023-03-01 00:48:36 -05:00
2020-01-13 13:32:55 -08:00
@IBOutlet private var versionLabel: UILabel!
2023-03-01 00:48:36 -05:00
2019-10-24 13:04:30 -07:00
override var preferredStatusBarStyle: UIStatusBarStyle {
2023-03-01 00:48:36 -05:00
.lightContent
2019-10-24 13:04:30 -07:00
}
2023-03-01 00:48:36 -05:00
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
2023-03-01 00:48:36 -05:00
2023-03-01 19:09:33 -05:00
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openPatreonSettings(_:)), name: SideStoreAppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
}
2023-03-01 00:48:36 -05:00
override func viewDidLoad() {
2019-06-06 14:46:23 -07:00
super.viewDidLoad()
2023-03-01 00:48:36 -05:00
2019-09-05 11:59:10 -07:00
let nib = UINib(nibName: "SettingsHeaderFooterView", bundle: nil)
2023-03-01 00:48:36 -05:00
prototypeHeaderFooterView = nib.instantiate(withOwner: nil, options: nil)[0] as? SettingsHeaderFooterView
tableView.register(nib, forHeaderFooterViewReuseIdentifier: "HeaderFooterView")
let debugModeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(SettingsViewController.handleDebugModeGesture(_:)))
debugModeGestureRecognizer.delegate = self
debugModeGestureRecognizer.direction = .up
debugModeGestureRecognizer.numberOfTouchesRequired = 3
2023-03-01 00:48:36 -05:00
tableView.addGestureRecognizer(debugModeGestureRecognizer)
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
versionLabel.text = NSLocalizedString(String(format: "SideStore %@", version), comment: "SideStore Version")
} else {
versionLabel.text = NSLocalizedString("SideStore", comment: "")
2020-01-13 13:32:55 -08:00
}
2023-03-01 00:48:36 -05:00
tableView.contentInset.bottom = 20
update()
if #available(iOS 15, *), let appearance = tabBarController?.tabBar.standardAppearance {
appearance.stackedLayoutAppearance.normal.badgeBackgroundColor = .altPrimary
self.navigationController?.tabBarItem.scrollEdgeAppearance = appearance
}
2019-06-06 14:46:23 -07:00
}
2023-03-01 00:48:36 -05:00
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
2023-03-01 00:48:36 -05:00
update()
}
2019-06-06 14:46:23 -07:00
}
2023-03-01 00:48:36 -05:00
private extension SettingsViewController {
func update() {
if let team = DatabaseManager.shared.activeTeam() {
accountNameLabel.text = team.name
accountEmailLabel.text = team.account.appleID
accountTypeLabel.text = team.type.localizedDescription
activeTeam = team
} else {
activeTeam = nil
2019-06-06 14:46:23 -07:00
}
2023-03-01 00:48:36 -05:00
backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
if isViewLoaded {
tableView.reloadData()
2019-06-06 14:46:23 -07:00
}
}
2023-03-01 00:48:36 -05:00
func prepare(_ settingsHeaderFooterView: SettingsHeaderFooterView, for section: Section, isHeader: Bool) {
2019-09-05 11:59:10 -07:00
settingsHeaderFooterView.primaryLabel.isHidden = !isHeader
settingsHeaderFooterView.secondaryLabel.isHidden = isHeader
settingsHeaderFooterView.button.isHidden = true
2023-03-01 00:48:36 -05:00
2019-09-05 11:59:10 -07:00
settingsHeaderFooterView.layoutMargins.bottom = isHeader ? 0 : 8
2023-03-01 00:48:36 -05:00
switch section {
2019-09-05 11:59:10 -07:00
case .signIn:
2023-03-01 00:48:36 -05:00
if isHeader {
2019-09-05 11:59:10 -07:00
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("ACCOUNT", comment: "")
2023-03-01 00:48:36 -05:00
} else {
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Sign in with your Apple ID to download apps from SideStore.", comment: "")
2019-09-05 11:59:10 -07:00
}
2023-03-01 00:48:36 -05:00
2019-09-05 11:59:10 -07:00
case .patreon:
2023-03-01 00:48:36 -05:00
if isHeader {
2019-09-25 12:43:32 -07:00
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("PATREON", comment: "")
2023-03-01 00:48:36 -05:00
} else {
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Support the SideStore Team by becoming a patron!", comment: "")
}
2019-09-05 11:59:10 -07:00
case .account:
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("ACCOUNT", comment: "")
2023-03-01 00:48:36 -05:00
2019-09-05 11:59:10 -07:00
settingsHeaderFooterView.button.setTitle(NSLocalizedString("SIGN OUT", comment: ""), for: .normal)
settingsHeaderFooterView.button.addTarget(self, action: #selector(SettingsViewController.signOut(_:)), for: .primaryActionTriggered)
settingsHeaderFooterView.button.isHidden = false
2023-03-01 00:48:36 -05:00
case .appRefresh:
2023-03-01 00:48:36 -05:00
if isHeader {
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("REFRESHING APPS", comment: "")
2023-03-01 00:48:36 -05:00
} else {
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi.", comment: "")
}
2023-03-01 00:48:36 -05:00
case .instructions:
break
2023-03-01 00:48:36 -05:00
case .credits:
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("CREDITS", comment: "")
2023-03-01 00:48:36 -05:00
2019-09-05 11:59:10 -07:00
case .debug:
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("DEBUG", comment: "")
}
}
2023-03-01 00:48:36 -05:00
func preferredHeight(for settingsHeaderFooterView: SettingsHeaderFooterView, in section: Section, isHeader: Bool) -> CGFloat {
2019-09-05 11:59:10 -07:00
let widthConstraint = settingsHeaderFooterView.contentView.widthAnchor.constraint(equalToConstant: tableView.bounds.width)
NSLayoutConstraint.activate([widthConstraint])
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
2023-03-01 00:48:36 -05:00
prepare(settingsHeaderFooterView, for: section, isHeader: isHeader)
2019-09-05 11:59:10 -07:00
let size = settingsHeaderFooterView.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
return size.height
}
2019-06-06 14:46:23 -07:00
}
2023-03-01 00:48:36 -05:00
private extension SettingsViewController {
func signIn() {
AppManager.shared.authenticate(presentingViewController: self) { result in
2019-06-06 14:46:23 -07:00
DispatchQueue.main.async {
2023-03-01 00:48:36 -05:00
switch result {
case .failure(OperationError.cancelled):
// Ignore
break
2023-03-01 00:48:36 -05:00
case let .failure(error):
let toastView = ToastView(error: error)
toastView.show(in: self)
2023-03-01 00:48:36 -05:00
case .success: break
}
2023-03-01 00:48:36 -05:00
2019-06-06 14:46:23 -07:00
self.update()
}
}
}
2023-03-01 00:48:36 -05:00
@objc func signOut(_ sender: UIBarButtonItem) {
func signOut() {
DatabaseManager.shared.signOut { error in
2019-06-06 14:46:23 -07:00
DispatchQueue.main.async {
2023-03-01 00:48:36 -05:00
if let error = error {
let toastView = ToastView(error: error)
toastView.show(in: self)
2019-06-06 14:46:23 -07:00
}
2023-03-01 00:48:36 -05:00
2019-06-06 14:46:23 -07:00
self.update()
}
}
}
2023-03-01 00:48:36 -05:00
2019-06-06 14:46:23 -07:00
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to sign out?", comment: ""), message: NSLocalizedString("You will no longer be able to install or refresh apps once you sign out.", comment: ""), preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Sign Out", comment: ""), style: .destructive) { _ in signOut() })
alertController.addAction(.cancel)
2023-03-01 00:48:36 -05:00
// Fix crash on iPad
2023-01-09 15:15:31 +08:00
alertController.popoverPresentationController?.barButtonItem = sender
2023-03-01 00:48:36 -05:00
present(alertController, animated: true, completion: nil)
2019-06-06 14:46:23 -07:00
}
2023-03-01 00:48:36 -05:00
@IBAction func toggleIsBackgroundRefreshEnabled(_ sender: UISwitch) {
2019-09-05 11:59:10 -07:00
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
}
2023-03-01 00:48:36 -05:00
@available(iOS 14, *)
2023-03-01 00:48:36 -05:00
@IBAction func addRefreshAppsShortcut() {
guard let shortcut = INShortcut(intent: INInteraction.refreshAllApps().intent) else { return }
2023-03-01 00:48:36 -05:00
let viewController = INUIAddVoiceShortcutViewController(shortcut: shortcut)
viewController.delegate = self
viewController.modalPresentationStyle = .formSheet
2023-03-01 00:48:36 -05:00
present(viewController, animated: true, completion: nil)
}
2023-03-01 00:48:36 -05:00
@IBAction func handleDebugModeGesture(_: UISwipeGestureRecognizer) {
debugGestureCounter += 1
debugGestureTimer?.invalidate()
if debugGestureCounter >= 3 {
debugGestureCounter = 0
UserDefaults.standard.isDebugModeEnabled.toggle()
2023-03-01 00:48:36 -05:00
tableView.reloadData()
} else {
debugGestureTimer = Timer.scheduledTimer(withTimeInterval: 0.4, repeats: false) { [weak self] _ in
self?.debugGestureCounter = 0
}
}
}
2023-03-01 00:48:36 -05:00
func openTwitter(username: String) {
let twitterAppURL = URL(string: "twitter://user?screen_name=" + username)!
2023-03-01 00:48:36 -05:00
UIApplication.shared.open(twitterAppURL, options: [:]) { success in
if success {
if let selectedIndexPath = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedIndexPath, animated: true)
}
2023-03-01 00:48:36 -05:00
} else {
let safariURL = URL(string: "https://twitter.com/" + username)!
2023-03-01 00:48:36 -05:00
let safariViewController = SFSafariViewController(url: safariURL)
safariViewController.preferredControlTintColor = .altPrimary
self.present(safariViewController, animated: true, completion: nil)
}
}
}
2019-06-06 14:46:23 -07:00
}
2023-03-01 00:48:36 -05:00
private extension SettingsViewController {
@objc func openPatreonSettings(_: Notification) {
guard presentedViewController == nil else { return }
UIView.performWithoutAnimation {
self.navigationController?.popViewController(animated: false)
self.performSegue(withIdentifier: "showPatreon", sender: nil)
}
}
}
2023-03-01 00:48:36 -05:00
extension SettingsViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
var numberOfSections = super.numberOfSections(in: tableView)
2023-03-01 00:48:36 -05:00
if !UserDefaults.standard.isDebugModeEnabled {
numberOfSections -= 1
}
2023-03-01 00:48:36 -05:00
return numberOfSections
}
2023-03-01 00:48:36 -05:00
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
2019-09-05 11:59:10 -07:00
let section = Section.allCases[section]
2023-03-01 00:48:36 -05:00
switch section {
case .signIn: return (activeTeam == nil) ? 1 : 0
case .account: return (activeTeam == nil) ? 0 : 3
case .appRefresh: return AppRefreshRow.allCases.count
2019-09-05 11:59:10 -07:00
default: return super.tableView(tableView, numberOfRowsInSection: section.rawValue)
}
}
2023-03-01 00:48:36 -05:00
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
2023-03-01 00:48:36 -05:00
if #available(iOS 14, *) {} else if let cell = cell as? InsetGroupTableViewCell,
indexPath.section == Section.appRefresh.rawValue,
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue {
// Only one row is visible pre-iOS 14.
cell.style = .single
}
2023-03-01 00:48:36 -05:00
return cell
}
2023-03-01 00:48:36 -05:00
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
2019-09-05 11:59:10 -07:00
let section = Section.allCases[section]
2023-03-01 00:48:36 -05:00
switch section {
case .signIn where activeTeam != nil: return nil
case .account where activeTeam == nil: return nil
case .signIn, .account, .patreon, .appRefresh, .credits, .debug:
2019-09-05 11:59:10 -07:00
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
2023-03-01 00:48:36 -05:00
prepare(headerView, for: section, isHeader: true)
2019-09-05 11:59:10 -07:00
return headerView
2023-03-01 00:48:36 -05:00
case .instructions: return nil
2019-09-05 11:59:10 -07:00
}
}
2023-03-01 00:48:36 -05:00
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
2019-09-05 11:59:10 -07:00
let section = Section.allCases[section]
2023-03-01 00:48:36 -05:00
switch section {
case .signIn where activeTeam != nil: return nil
case .signIn, .patreon, .appRefresh:
2019-09-05 11:59:10 -07:00
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
2023-03-01 00:48:36 -05:00
prepare(footerView, for: section, isHeader: false)
2019-09-05 11:59:10 -07:00
return footerView
2023-03-01 00:48:36 -05:00
case .account, .credits, .debug, .instructions: return nil
2019-09-05 11:59:10 -07:00
}
2019-06-06 14:46:23 -07:00
}
2023-03-01 00:48:36 -05:00
override func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
2019-09-05 11:59:10 -07:00
let section = Section.allCases[section]
2023-03-01 00:48:36 -05:00
switch section {
case .signIn where activeTeam != nil: return 1.0
case .account where activeTeam == nil: return 1.0
case .signIn, .account, .patreon, .appRefresh, .credits, .debug:
2023-03-01 00:48:36 -05:00
let height = preferredHeight(for: prototypeHeaderFooterView, in: section, isHeader: true)
2019-09-05 11:59:10 -07:00
return height
2023-03-01 00:48:36 -05:00
case .instructions: return 0.0
2019-09-05 11:59:10 -07:00
}
}
2023-03-01 00:48:36 -05:00
override func tableView(_: UITableView, heightForFooterInSection section: Int) -> CGFloat {
2019-09-05 11:59:10 -07:00
let section = Section.allCases[section]
2023-03-01 00:48:36 -05:00
switch section {
case .signIn where activeTeam != nil: return 1.0
case .account where activeTeam == nil: return 1.0
case .signIn, .patreon, .appRefresh:
2023-03-01 00:48:36 -05:00
let height = preferredHeight(for: prototypeHeaderFooterView, in: section, isHeader: false)
2019-09-05 11:59:10 -07:00
return height
2023-03-01 00:48:36 -05:00
case .account, .credits, .debug, .instructions: return 0.0
2019-09-05 11:59:10 -07:00
}
}
}
2019-06-06 14:46:23 -07:00
2023-03-01 00:48:36 -05:00
extension SettingsViewController {
override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
2019-09-05 11:59:10 -07:00
let section = Section.allCases[indexPath.section]
2023-03-01 00:48:36 -05:00
switch section {
case .signIn: signIn()
case .instructions: break
case .appRefresh:
let row = AppRefreshRow.allCases[indexPath.row]
2023-03-01 00:48:36 -05:00
switch row {
case .backgroundRefresh: break
case .addToSiri:
guard #available(iOS 14, *) else { return }
2023-03-01 00:48:36 -05:00
addRefreshAppsShortcut()
}
2023-03-01 00:48:36 -05:00
case .credits:
let row = CreditsRow.allCases[indexPath.row]
2023-03-01 00:48:36 -05:00
switch row {
case .developer: openTwitter(username: "sidestore_io")
case .operations: openTwitter(username: "sidestore_io")
case .designer: openTwitter(username: "lit_ritt")
case .softwareLicenses: break
}
2023-03-01 00:48:36 -05:00
case .debug:
let row = DebugRow.allCases[indexPath.row]
2023-03-01 00:48:36 -05:00
switch row {
case .sendFeedback:
2023-03-01 00:48:36 -05:00
if MFMailComposeViewController.canSendMail() {
let mailViewController = MFMailComposeViewController()
mailViewController.mailComposeDelegate = self
mailViewController.setToRecipients(["support@sidestore.io"])
2023-03-01 00:48:36 -05:00
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
mailViewController.setSubject("SideStore Beta \(version) Feedback")
2023-03-01 00:48:36 -05:00
} else {
mailViewController.setSubject("SideStore Beta Feedback")
}
2023-03-01 00:48:36 -05:00
present(mailViewController, animated: true, completion: nil)
} else {
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
toastView.show(in: self)
}
2023-01-09 15:15:31 +08:00
case .resetPairingFile:
let filename = "ALTPairingFile.mobiledevicepairing"
let fm = FileManager.default
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
let alertController = UIAlertController(
title: NSLocalizedString("Are you sure to reset the pairing file?", comment: ""),
message: NSLocalizedString("You can reset the pairing file when you cannot sideload apps or enable JIT. You need to restart SideStore.", comment: ""),
2023-03-01 00:48:36 -05:00
preferredStyle: UIAlertController.Style.actionSheet
)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete and Reset", comment: ""), style: .destructive) { _ in
2023-01-09 15:15:31 +08:00
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty {
try? fm.removeItem(atPath: documentsPath.path)
NSLog("Pairing File Reseted")
}
self.tableView.deselectRow(at: indexPath, animated: true)
let dialogMessage = UIAlertController(title: NSLocalizedString("Pairing File Reseted", comment: ""), message: NSLocalizedString("Please restart SideStore", comment: ""), preferredStyle: .alert)
self.present(dialogMessage, animated: true, completion: nil)
})
alertController.addAction(.cancel)
2023-03-01 00:48:36 -05:00
// Fix crash on iPad
alertController.popoverPresentationController?.sourceView = tableView
alertController.popoverPresentationController?.sourceRect = tableView.rectForRow(at: indexPath)
present(alertController, animated: true)
tableView.deselectRow(at: indexPath, animated: true)
case .advancedSettings:
// Create the URL that deep links to your app's custom settings.
if let url = URL(string: UIApplication.openSettingsURLString) {
// Ask the system to open that URL.
UIApplication.shared.open(url)
} else {
ELOG("UIApplication.openSettingsURLString invalid")
}
case .refreshAttempts, .errorLog: break
}
2023-03-01 00:48:36 -05:00
2019-09-05 11:59:10 -07:00
default: break
}
}
}
2023-03-01 00:48:36 -05:00
extension SettingsViewController: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith _: MFMailComposeResult, error: Error?) {
if let error = error {
let toastView = ToastView(error: error)
toastView.show(in: self)
}
2023-03-01 00:48:36 -05:00
controller.dismiss(animated: true, completion: nil)
}
}
2023-03-01 00:48:36 -05:00
extension SettingsViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer) -> Bool {
true
}
}
2023-03-01 00:48:36 -05:00
extension SettingsViewController: INUIAddVoiceShortcutViewControllerDelegate {
func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith _: INVoiceShortcut?, error: Error?) {
if let indexPath = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: indexPath, animated: true)
}
2023-03-01 00:48:36 -05:00
controller.dismiss(animated: true, completion: nil)
2023-03-01 00:48:36 -05:00
guard let error = error else { return }
2023-03-01 00:48:36 -05:00
let toastView = ToastView(error: error)
toastView.show(in: self)
}
2023-03-01 00:48:36 -05:00
func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) {
if let indexPath = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: indexPath, animated: true)
}
2023-03-01 00:48:36 -05:00
controller.dismiss(animated: true, completion: nil)
}
}