diff --git a/AltStore/App Detail/AppViewController.swift b/AltStore/App Detail/AppViewController.swift index ffba2ba1..7977ce79 100644 --- a/AltStore/App Detail/AppViewController.swift +++ b/AltStore/App Detail/AppViewController.swift @@ -510,7 +510,7 @@ extension AppViewController catch { DispatchQueue.main.async { - let toastView = ToastView(error: error) + let toastView = ToastView(error: error, opensLog: true) toastView.show(in: self) } } diff --git a/AltStore/Browse/BrowseViewController.swift b/AltStore/Browse/BrowseViewController.swift index a23fbbe9..21ab4073 100644 --- a/AltStore/Browse/BrowseViewController.swift +++ b/AltStore/Browse/BrowseViewController.swift @@ -278,7 +278,7 @@ private extension BrowseViewController { case .failure(OperationError.cancelled): break // Ignore case .failure(let error): - let toastView = ToastView(error: error) + let toastView = ToastView(error: error, opensLog: true) toastView.show(in: self) case .success: print("Installed app:", app.bundleIdentifier) diff --git a/AltStore/Components/ToastView.swift b/AltStore/Components/ToastView.swift index 5aba48e7..4cbe6db2 100644 --- a/AltStore/Components/ToastView.swift +++ b/AltStore/Components/ToastView.swift @@ -18,8 +18,17 @@ extension TimeInterval final class ToastView: RSTToastView { + static let openErrorLogNotification = Notification.Name("ALTOpenErrorLogNotification") + var preferredDuration: TimeInterval - + + var opensErrorLog: Bool = false + + convenience init(text: String, detailText: String?, opensLog: Bool = false) { + self.init(text: text, detailText: detailText) + self.opensErrorLog = opensLog + } + override init(text: String, detailText detailedText: String?) { if detailedText == nil @@ -43,9 +52,16 @@ final class ToastView: RSTToastView // RSTToastView does not expose stack view containing labels, // so we access it indirectly as the labels' superview. stackView.spacing = (detailedText != nil) ? 4.0 : 0.0 + stackView.alignment = .leading } + self.addTarget(self, action: #selector(ToastView.showErrorLog), for: .touchUpInside) } - + + convenience init(error: Error, opensLog: Bool = false) { + self.init(error: error) + self.opensErrorLog = opensLog + } + convenience init(error: Error) { var error = error as NSError @@ -95,6 +111,18 @@ final class ToastView: RSTToastView override func show(in view: UIView, duration: TimeInterval) { + if opensErrorLog, #available(iOS 13.0, *), case let configuration = UIImage.SymbolConfiguration(font: self.textLabel.font), + let icon = UIImage(systemName: "chevron.right.circle", withConfiguration: configuration) { + let tintedIcon = icon.withTintColor(.white, renderingMode: .alwaysOriginal) + let moreIconImageView = UIImageView(image: tintedIcon) + moreIconImageView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(moreIconImageView) + NSLayoutConstraint.activate([ + moreIconImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -self.layoutMargins.right), + moreIconImageView.centerYAnchor.constraint(equalTo: self.textLabel.centerYAnchor), + moreIconImageView.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: self.textLabel.trailingAnchor, multiplier: 1.0) + ]) + } super.show(in: view, duration: duration) let announcement = (self.textLabel.text ?? "") + ". " + (self.detailTextLabel.text ?? "") @@ -110,4 +138,10 @@ final class ToastView: RSTToastView { self.show(in: view, duration: self.preferredDuration) } + + @objc + func showErrorLog() { + guard self.opensErrorLog else { return } + NotificationCenter.default.post(name: ToastView.openErrorLogNotification, object: self) + } } diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index 298f502c..3a3cb08c 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -535,11 +535,9 @@ private extension MyAppsViewController guard !failures.isEmpty else { return } - let toastView: ToastView - if let failure = failures.first, results.count == 1 { - toastView = ToastView(error: failure.value) + ToastView(error: failure.value).show(in: self) } else { @@ -557,11 +555,10 @@ private extension MyAppsViewController let error = failures.first?.value as NSError? let detailText = error?.localizedFailure ?? error?.localizedFailureReason ?? error?.localizedDescription - toastView = ToastView(text: localizedText, detailText: detailText) + let toastView = ToastView(text: localizedText, detailText: detailText, opensLog: true) toastView.preferredDuration = 4.0 + toastView.show(in: self) } - - toastView.show(in: self) } self.refreshGroup = nil @@ -697,8 +694,7 @@ private extension MyAppsViewController self.collectionView.reloadItems(at: [indexPath]) case .failure(let error): - let toastView = ToastView(error: error) - toastView.show(in: self) + ToastView(error: error, opensLog: true).show(in: self) self.collectionView.reloadItems(at: [indexPath]) @@ -899,9 +895,8 @@ private extension MyAppsViewController completion(.failure((OperationError.cancelled))) case .failure(let error): - let toastView = ToastView(error: error) - toastView.show(in: self) - + ToastView(error: error, opensLog: true).show(in: self) + completion(.failure(error)) } } @@ -1015,8 +1010,7 @@ private extension MyAppsViewController UIApplication.shared.open(installedApp.openAppURL) { success in guard !success else { return } - let toastView = ToastView(error: OperationError.openAppFailed(name: installedApp.name)) - toastView.show(in: self) + ToastView(error: OperationError.openAppFailed(name: installedApp.name), opensLog: true).show(in: self) } } @@ -1067,8 +1061,7 @@ private extension MyAppsViewController DispatchQueue.main.async { installedApp.isActive = false - let toastView = ToastView(error: error) - toastView.show(in: self) + ToastView(error: error, opensLog: true).show(in: self) } } } @@ -1141,8 +1134,7 @@ private extension MyAppsViewController DispatchQueue.main.async { installedApp.isActive = true - let toastView = ToastView(error: error) - toastView.show(in: self) + ToastView(error: error, opensLog: true).show(in: self) } } @@ -1173,8 +1165,7 @@ private extension MyAppsViewController case .success: break case .failure(let error): DispatchQueue.main.async { - let toastView = ToastView(error: error) - toastView.show(in: self) + ToastView(error: error, opensLog: true).show(in: self) } } } @@ -1208,9 +1199,8 @@ private extension MyAppsViewController print("Failed to back up app:", error) DispatchQueue.main.async { - let toastView = ToastView(error: error) - toastView.show(in: self) - + ToastView(error: error, opensLog: true).show(in: self) + self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue]) } } @@ -1245,8 +1235,7 @@ private extension MyAppsViewController print("Failed to restore app:", error) DispatchQueue.main.async { - let toastView = ToastView(error: error) - toastView.show(in: self) + ToastView(error: error, opensLog: true).show(in: self) } } } @@ -1321,8 +1310,7 @@ private extension MyAppsViewController print("Failed to change app icon.", error) DispatchQueue.main.async { - let toastView = ToastView(error: error) - toastView.show(in: self) + ToastView(error: error, opensLog: true).show(in: self) } } } @@ -1334,9 +1322,8 @@ private extension MyAppsViewController guard minimuxerStatus else { return } if #available(iOS 17, *) { - let toastView = ToastView(error: (OperationError.tooNewError as NSError).withLocalizedTitle("No iOS 17 On Device JIT!")) + ToastView(error: (OperationError.tooNewError as NSError).withLocalizedTitle("No iOS 17 On Device JIT!"), opensLog: true).show(in: self) AppManager.shared.log(OperationError.tooNewError, operation: .enableJIT, app: installedApp) - toastView.show(in: self) return } @@ -1346,8 +1333,8 @@ private extension MyAppsViewController { case .success: break case .failure(let error): - let toastView = ToastView(error: error) - toastView.show(in: self.navigationController?.view ?? self.view, duration: 5) + ToastView(error: error, opensLog: true).show(in: self) + AppManager.shared.log(error, operation: .enableJIT, app: installedApp) } } } diff --git a/AltStore/News/NewsViewController.swift b/AltStore/News/NewsViewController.swift index 945a0822..f57e9117 100644 --- a/AltStore/News/NewsViewController.swift +++ b/AltStore/News/NewsViewController.swift @@ -313,9 +313,8 @@ private extension NewsViewController { case .failure(OperationError.cancelled): break // Ignore case .failure(let error): - let toastView = ToastView(error: error) - toastView.show(in: self) - + ToastView(error: error, opensLog: true).show(in: self) + case .success: print("Installed app:", storeApp.bundleIdentifier) } diff --git a/AltStore/Settings/Settings.storyboard b/AltStore/Settings/Settings.storyboard index 999bcb52..a0b20cbb 100644 --- a/AltStore/Settings/Settings.storyboard +++ b/AltStore/Settings/Settings.storyboard @@ -21,7 +21,7 @@