diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 521510b..fe88ad9 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -13,7 +13,7 @@ class LibraryViewController: UITableViewController { var installer: Installer? - public var searchController: UISearchController! + var searchController: UISearchController! var popupVC: PopupViewController! var loaderAlert: UIAlertController? @@ -339,7 +339,7 @@ extension LibraryViewController { override func numberOfSections(in _: UITableView) -> Int { return 2 } - + override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int { switch section { case 0: @@ -350,11 +350,11 @@ extension LibraryViewController { return 0 } } - + override func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? { switch section { case 0: - let headerWithButton = GroupedSectionHeader( + return GroupedSectionHeader( title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS"), subtitle: String.localized( "LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS_TOTAL", @@ -365,47 +365,45 @@ extension LibraryViewController { self?.startImporting() } ) - return headerWithButton - + case 1: - let headerWithButton = GroupedSectionHeader( + return GroupedSectionHeader( title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_DOWNLOADED_APPS"), subtitle: String.localized( "LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_DOWNLOADED_APPS_TOTAL", arguments: String(downloadedApps?.count ?? 0) ) ) - return headerWithButton - + default: return nil } } - + override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = AppsTableViewCell(style: .subtitle, reuseIdentifier: "RoundedBackgroundCell") cell.selectionStyle = .default cell.accessoryType = .disclosureIndicator cell.backgroundColor = .clear - + guard let source = getApplication(row: indexPath.row, section: indexPath.section), let filePath = getApplicationFilePath( - with: source, - row: indexPath.row, - section: indexPath.section + with: source, + row: indexPath.row, + section: indexPath.section ) else { return cell } - + configureCell(cell, with: source, filePath: filePath) return cell } - + private func configureCell(_ cell: AppsTableViewCell, with app: NSManagedObject, filePath: URL) { if let iconURL = app.value(forKey: "iconURL") as? String { let imagePath = filePath.appendingPathComponent(iconURL) - + if let image = CoreDataManager.shared.loadImage(from: imagePath) { SectionIcons.sectionImage(to: cell, with: image) } else if let defaultImage = UIImage(named: "unknown") { @@ -414,456 +412,458 @@ extension LibraryViewController { } else if let defaultImage = UIImage(named: "unknown") { SectionIcons.sectionImage(to: cell, with: defaultImage) } - + cell.configure(with: app, filePath: filePath) } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let app = getApplication(row: indexPath.row, section: indexPath.section), let fullPath = getApplicationFilePath( - with: app, - row: indexPath.row, - section: indexPath.section, - getuuidonly: false + with: app, + row: indexPath.row, + section: indexPath.section, + getuuidonly: false ) else { tableView.deselectRow(at: indexPath, animated: true) return } - let uuidOnlyPath = getApplicationFilePath( - with: app, - row: indexPath.row, - section: indexPath.section, - getuuidonly: true - ) - - let appName = app.value(forKey: "name") as? String ?? "" - - if !FileManager.default.fileExists(atPath: fullPath.path) { - backdoor.Debug.shared.log( - message: "The file has been deleted for this entry, please remove it manually.", - type: .critical + let uuidOnlyPath = getApplicationFilePath( + with: app, + row: indexPath.row, + section: indexPath.section, + getuuidonly: true ) - tableView.deselectRow(at: indexPath, animated: true) - return - } - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet + let appName = app.value(forKey: "name") as? String ?? "" - switch indexPath.section { - case 0: - handleSignedAppAction( - app: app, - uuidPath: uuidOnlyPath, - fullPath: fullPath, - appName: appName, - indexPath: indexPath - ) - case 1: - handleDownloadedAppAction( - app: app, - uuidPath: uuidOnlyPath, - appName: appName - ) - default: - break - } + if !FileManager.default.fileExists(atPath: fullPath.path) { + backdoor.Debug.shared.log( + message: "The file has been deleted for this entry, please remove it manually.", + type: .critical + ) + tableView.deselectRow(at: indexPath, animated: true) + return + } - tableView.deselectRow(at: indexPath, animated: true) -} + popupVC = PopupViewController() + popupVC.modalPresentationStyle = .pageSheet -private func handleSignedAppAction( - app: NSManagedObject, - uuidPath: URL?, - fullPath: URL, - appName: String, - indexPath: IndexPath -) { - let hasUpdate = (app as? SignedApps)?.value(forKey: "hasUpdate") as? Bool ?? false - - if let signedApp = app as? SignedApps, hasUpdate { - configureUpdateMenuButtons(for: signedApp, appName: appName, indexPath: indexPath) - } else { - configureRegularMenuButtons( - for: app, - uuidPath: uuidPath, - fullPath: fullPath, - appName: appName, - indexPath: indexPath - ) + switch indexPath.section { + case 0: + handleSignedAppAction( + app: app, + uuidPath: uuidOnlyPath, + fullPath: fullPath, + appName: appName, + indexPath: indexPath + ) + case 1: + handleDownloadedAppAction( + app: app, + uuidPath: uuidOnlyPath, + appName: appName + ) + default: + break + } + + tableView.deselectRow(at: indexPath, animated: true) } - configurePopupDetents(hasUpdate: hasUpdate) - present(popupVC, animated: true) -} + private func handleSignedAppAction( + app: NSManagedObject, + uuidPath: URL?, + fullPath: URL, + appName: String, + indexPath: IndexPath + ) { + let hasUpdate = (app as? SignedApps)?.value(forKey: "hasUpdate") as? Bool ?? false -private func configureUpdateMenuButtons( - for signedApp: SignedApps, - appName: String, - indexPath: IndexPath -) { - // Update available menu - let updateButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE", arguments: appName), - color: .tintColor.withAlphaComponent(0.9), - titleColor: .white - ) - updateButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - self.handleAppUpdate(for: signedApp) - } - } - - let clearButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE"), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - clearButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - do { - try CoreDataManager.shared.clearUpdateStateCompat(for: signedApp) - self.tableView.reloadRows(at: [indexPath], with: .none) - } catch { - backdoor.Debug.shared.log(message: "Error clearing update state: \(error)", type: LogType.error) + if let signedApp = app as? SignedApps, hasUpdate { + configureUpdateMenuButtons(for: signedApp, appName: appName, indexPath: indexPath) + } else { + configureRegularMenuButtons( + for: app, + uuidPath: uuidPath, + fullPath: fullPath, + appName: appName, + indexPath: indexPath + ) } + + configurePopupDetents(hasUpdate: hasUpdate) + present(popupVC, animated: true) } - popupVC.configureButtons([updateButton, clearButton]) -} + private func configureUpdateMenuButtons( + for signedApp: SignedApps, + appName: String, + indexPath: IndexPath + ) { + // Update available menu + let updateButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE", arguments: appName), + color: .tintColor.withAlphaComponent(0.9), + titleColor: .white + ) + updateButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + self.handleAppUpdate(for: signedApp) + } + } -private func configureRegularMenuButtons( - for app: NSManagedObject, - uuidPath: URL?, - fullPath: URL, - appName: String, - indexPath: IndexPath -) { - // Install button - let installButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), - color: .tintColor.withAlphaComponent(0.9) - ) - installButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startInstallProcess(app: app, filePath: uuidPath?.path ?? "") - } - - // Open button - let openButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - openButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - - if let workspace = LSApplicationWorkspace.default(), - let bundleID = app.value(forKey: "bundleidentifier") as? String - { - let success = workspace.openApplication(withBundleID: bundleID) - if !success { - backdoor.Debug.shared.log( - message: "Unable to open, do you have the app installed?", - type: LogType.warning - ) + let clearButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE"), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + clearButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + do { + try CoreDataManager.shared.clearUpdateStateCompat(for: signedApp) + self.tableView.reloadRows(at: [indexPath], with: .none) + } catch { + backdoor.Debug.shared.log(message: "Error clearing update state: \(error)", type: LogType.error) } } + + popupVC.configureButtons([updateButton, clearButton]) } - // Resign button - let resignButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_RESIGN", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - resignButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - self.handleResignApp(app: app, fullPath: fullPath, indexPath: indexPath) + private func configureRegularMenuButtons( + for app: NSManagedObject, + uuidPath: URL?, + fullPath: URL, + appName: String, + indexPath: IndexPath + ) { + // Install button + let installButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), + color: .tintColor.withAlphaComponent(0.9) + ) + installButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startInstallProcess(app: app, filePath: uuidPath?.path ?? "") } - } - // Share button - let shareButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - shareButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.shareFile(app: app, filePath: uuidPath?.path ?? "") - } + // Open button + let openButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + openButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) - popupVC.configureButtons([installButton, openButton, resignButton, shareButton]) -} + if let workspace = LSApplicationWorkspace.default(), + let bundleID = app.value(forKey: "bundleidentifier") as? String + { + let success = workspace.openApplication(withBundleID: bundleID) + if !success { + backdoor.Debug.shared.log( + message: "Unable to open, do you have the app installed?", + type: LogType.warning + ) + } + } + } -private func handleResignApp(app: NSManagedObject, fullPath: URL, indexPath: IndexPath) { - guard let signedApp = app as? SignedApps else { return } + // Resign button + let resignButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_RESIGN", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + resignButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + self.handleResignApp(app: app, fullPath: fullPath, indexPath: indexPath) + } + } - if let cert = CoreDataManager.shared.getCurrentCertificate() { - if let loaderAlert = loaderAlert { - present(loaderAlert, animated: true) + // Share button + let shareButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + shareButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.shareFile(app: app, filePath: uuidPath?.path ?? "") } - resignApp(certificate: cert, appPath: fullPath) { [weak self] success in - guard let self = self, success else { return } + popupVC.configureButtons([installButton, openButton, resignButton, shareButton]) + } - if let expirationDate = cert.certData?.expirationDate, - let teamName = cert.certData?.name - { - // Use the explicit error completion version with full type qualification - let updateSignedApp: (SignedApps, Date, String, @escaping (Error?) -> Void) -> Void = CoreDataManager - .shared.updateSignedApp - updateSignedApp( - signedApp, - expirationDate, - teamName, - { _ in - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - backdoor.Debug.shared.log(message: "Resign completed") - self.tableView.reloadRows(at: [indexPath], with: .left) + private func handleResignApp(app: NSManagedObject, fullPath: URL, indexPath: IndexPath) { + guard let signedApp = app as? SignedApps else { return } + + if let cert = CoreDataManager.shared.getCurrentCertificate() { + if let loaderAlert = loaderAlert { + present(loaderAlert, animated: true) + } + + resignApp(certificate: cert, appPath: fullPath) { [weak self] success in + guard let self = self, success else { return } + + if let expirationDate = cert.certData?.expirationDate, + let teamName = cert.certData?.name + { + // Use the explicit error completion version with full type qualification + let updateSignedApp: (SignedApps, Date, String, @escaping (Error?) -> Void) + -> Void = CoreDataManager + .shared.updateSignedApp + updateSignedApp( + signedApp, + expirationDate, + teamName, + { _ in + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + backdoor.Debug.shared.log(message: "Resign completed") + self.tableView.reloadRows(at: [indexPath], with: .left) + } } - } - ) + ) + } } + } else { + showNoCertificatesAlert() } - } else { - showNoCertificatesAlert() } -} private func showNoCertificatesAlert() { - let alert = UIAlertController( - title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), - message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default)) - present(alert, animated: true) -} + let alert = UIAlertController( + title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), + message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default)) + present(alert, animated: true) + } private func handleDownloadedAppAction( - app: NSManagedObject, - uuidPath: URL?, - appName: String -) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - - // Sign button - let signButton = PopupViewControllerButton( - title: signingDataWrapper.signingOptions.installAfterSigned - ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) - : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), - color: .tintColor.withAlphaComponent(0.9) - ) - signButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startSigning(app: app) - } - - // Install button - let installButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - installButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - self.showInstallConfirmationAlert(app: app, filePath: uuidPath?.path ?? "") - } - } - - popupVC.configureButtons([signButton, installButton]) - configurePopupDetents(hasUpdate: false) - present(popupVC, animated: true) -} + app: NSManagedObject, + uuidPath: URL?, + appName: String + ) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + + // Sign button + let signButton = PopupViewControllerButton( + title: signingDataWrapper.signingOptions.installAfterSigned + ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) + : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), + color: .tintColor.withAlphaComponent(0.9) + ) + signButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startSigning(app: app) + } + + // Install button + let installButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + installButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + self.showInstallConfirmationAlert(app: app, filePath: uuidPath?.path ?? "") + } + } + + popupVC.configureButtons([signButton, installButton]) + configurePopupDetents(hasUpdate: false) + present(popupVC, animated: true) + } private func showInstallConfirmationAlert(app: NSManagedObject, filePath: String) { - let alertController = UIAlertController( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), - message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), - preferredStyle: .alert - ) - - let confirmAction = UIAlertAction( - title: String.localized("INSTALL"), - style: .default - ) { [weak self] _ in - self?.startInstallProcess(app: app, filePath: filePath) - } - - let cancelAction = UIAlertAction( - title: String.localized("CANCEL"), - style: .cancel - ) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - present(alertController, animated: true) -} + let alertController = UIAlertController( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), + message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), + preferredStyle: .alert + ) + + let confirmAction = UIAlertAction( + title: String.localized("INSTALL"), + style: .default + ) { [weak self] _ in + self?.startInstallProcess(app: app, filePath: filePath) + } + + let cancelAction = UIAlertAction( + title: String.localized("CANCEL"), + style: .cancel + ) + + alertController.addAction(confirmAction) + alertController.addAction(cancelAction) + present(alertController, animated: true) + } private func configurePopupDetents(hasUpdate: Bool) { - let detentHeight = hasUpdate ? 150.0 : 270.0 - let detent: UISheetPresentationController.Detent = ._detent( - withIdentifier: "PopupDetent", - constant: detentHeight - ) + let detentHeight = hasUpdate ? 150.0 : 270.0 + let detent: UISheetPresentationController.Detent = ._detent( + withIdentifier: "PopupDetent", + constant: detentHeight + ) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [detent, .medium()] - presentationController.prefersGrabberVisible = true + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [detent, .medium()] + presentationController.prefersGrabberVisible = true + } } -} -// Implementation moved to LibraryViewController+Types extension + // Implementation moved to LibraryViewController+Types extension -// MARK: - Legacy method for backward compatibility + // MARK: - Legacy method for backward compatibility -// This method is kept for compatibility with existing code + // This method is kept for compatibility with existing code @available(*, deprecated, message: "Use startSigning(app:) instead") func startSigning(meow: NSManagedObject) { - // Call the method with the original parameter name to match caller expectations - startSigning(meow: meow) -} - - override func tableView( - _: UITableView, - trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath -) -> UISwipeActionsConfiguration? { - // Access method directly without self, ensuring scope is correct - guard let app = getApplication(row: indexPath.row, section: indexPath.section) else { - return nil + // Call the method with the original parameter name to match caller expectations + startSigning(meow: meow) } - let deleteAction = UIContextualAction( - style: .destructive, - title: String.localized("DELETE") - ) { [weak self] _, _, completionHandler in - guard let self = self else { - completionHandler(false) - return + override func tableView( + _: UITableView, + trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath + ) -> UISwipeActionsConfiguration? { + // Access method directly without self, ensuring scope is correct + guard let app = getApplication(row: indexPath.row, section: indexPath.section) else { + return nil } - do { - switch indexPath.section { - case 0: - if let signedApp = app as? SignedApps { - try CoreDataManager.shared.deleteAllSignedAppContentWithThrow(for: signedApp) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - } - case 1: - if let downloadedApp = app as? DownloadedApps { - try CoreDataManager.shared.deleteAllDownloadedAppContentWithThrow(for: downloadedApp) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + let deleteAction = UIContextualAction( + style: .destructive, + title: String.localized("DELETE") + ) { [weak self] _, _, completionHandler in + guard let self = self else { + completionHandler(false) + return + } + + do { + switch indexPath.section { + case 0: + if let signedApp = app as? SignedApps { + try CoreDataManager.shared.deleteAllSignedAppContentWithThrow(for: signedApp) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + } + case 1: + if let downloadedApp = app as? DownloadedApps { + try CoreDataManager.shared.deleteAllDownloadedAppContentWithThrow(for: downloadedApp) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + } + default: + break } - default: - break + completionHandler(true) + } catch { + backdoor.Debug.shared.log( + message: "Error deleting app: \(error)", + type: LogType.error + ) + completionHandler(false) } - completionHandler(true) - } catch { - backdoor.Debug.shared.log( - message: "Error deleting app: \(error)", - type: LogType.error - ) - completionHandler(false) } - } - deleteAction.backgroundColor = UIColor.systemRed - let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) - configuration.performsFirstActionWithFullSwipe = true - - return configuration -} + deleteAction.backgroundColor = UIColor.systemRed + let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) + configuration.performsFirstActionWithFullSwipe = true - override func tableView( - _: UITableView, - contextMenuConfigurationForRowAt indexPath: IndexPath, - point _: CGPoint -) -> UIContextMenuConfiguration? { - // Access methods directly without self, ensuring scope is correct - guard let app = getApplication(row: indexPath.row, section: indexPath.section), - let filePath = getApplicationFilePath( - with: app, - row: indexPath.row, - section: indexPath.section - ) - else { - return nil + return configuration } - return UIContextMenuConfiguration( - identifier: nil, - actionProvider: { [weak self] _ in - guard let self = self else { return UIMenu() } - - var actions: [UIAction] = [] - - // View details action - let infoAction = UIAction( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS"), - image: UIImage(systemName: "info.circle") - ) { [weak self] _ in - guard let self = self else { return } - - let viewController = AppsInformationViewController() - viewController.source = app - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) + override func tableView( + _: UITableView, + contextMenuConfigurationForRowAt indexPath: IndexPath, + point _: CGPoint + ) -> UIContextMenuConfiguration? { + // Access methods directly without self, ensuring scope is correct + guard let app = getApplication(row: indexPath.row, section: indexPath.section), + let filePath = getApplicationFilePath( + with: app, + row: indexPath.row, + section: indexPath.section + ) + else { + return nil + } - if #available(iOS 15.0, *) { - if let presentationController = navigationController - .presentationController as? UISheetPresentationController - { - presentationController.detents = [.medium(), .large()] + return UIContextMenuConfiguration( + identifier: nil, + actionProvider: { [weak self] _ in + guard let self = self else { return UIMenu() } + + var actions: [UIAction] = [] + + // View details action + let infoAction = UIAction( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS"), + image: UIImage(systemName: "info.circle") + ) { [weak self] _ in + guard let self = self else { return } + + let viewController = AppsInformationViewController() + viewController.source = app + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController + .presentationController as? UISheetPresentationController + { + presentationController.detents = [.medium(), .large()] + } } - } - self.present(navigationController, animated: true) - } - actions.append(infoAction) - - // Open in Files action - let filesAction = UIAction( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES"), - image: UIImage(systemName: "folder") - ) { _ in - let parentPath = filePath.deletingLastPathComponent() - let documentURL = parentPath.absoluteString.replacingOccurrences( - of: "file://", - with: "shareddocuments://" - ) + self.present(navigationController, animated: true) + } + actions.append(infoAction) + + // Open in Files action + let filesAction = UIAction( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES"), + image: UIImage(systemName: "folder") + ) { _ in + let parentPath = filePath.deletingLastPathComponent() + let documentURL = parentPath.absoluteString.replacingOccurrences( + of: "file://", + with: "shareddocuments://" + ) - if let url = URL(string: documentURL) { - UIApplication.shared.open(url, options: [:]) { success in - if success { - backdoor.Debug.shared.log(message: "File opened successfully.") - } else { - backdoor.Debug.shared.log(message: "Failed to open file.") + if let url = URL(string: documentURL) { + UIApplication.shared.open(url, options: [:]) { success in + if success { + backdoor.Debug.shared.log(message: "File opened successfully.") + } else { + backdoor.Debug.shared.log(message: "Failed to open file.") + } } + } else { + backdoor.Debug.shared.log(message: "Invalid file URL", type: LogType.error) } - } else { - backdoor.Debug.shared.log(message: "Invalid file URL", type: LogType.error) } - } - actions.append(filesAction) + actions.append(filesAction) - return UIMenu(title: "", children: actions) - } - ) + return UIMenu(title: "", children: actions) + } + ) + } } // MARK: - Fetch Data Extension diff --git a/iOS/Views/Hub/WebViewController.swift b/iOS/Views/Hub/WebViewController.swift index a51e876..ab2664a 100644 --- a/iOS/Views/Hub/WebViewController.swift +++ b/iOS/Views/Hub/WebViewController.swift @@ -1,6 +1,6 @@ import SafariServices import UIKit -import WebKit +@preconcurrency import WebKit /// Enhanced WebViewController for BDG Hub with modern UI and features @preconcurrency class WebViewController: UIViewController, WKNavigationDelegate, UIScrollViewDelegate {