[ADD] Error log view

This commit is contained in:
Fabian Thies
2023-02-04 13:07:04 +01:00
committed by Joe Mattiello
parent e0bd54389c
commit 07159b0ea6
6 changed files with 289 additions and 1 deletions

View File

@@ -37,6 +37,9 @@
1F180F94298E7A2500D1C98B /* Source+Trusted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F180F93298E7A2500D1C98B /* Source+Trusted.swift */; };
1F2EF787297C4D40002FD839 /* LicensesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2EF786297C4D40002FD839 /* LicensesView.swift */; };
1F44634529744E570070E514 /* HintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F44634429744E570070E514 /* HintView.swift */; };
1F545E83298D79E400589F68 /* ErrorLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F545E82298D79E400589F68 /* ErrorLogView.swift */; };
1F545E85298D84CF00589F68 /* FilePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F545E84298D84CF00589F68 /* FilePreviewView.swift */; };
1F545E87298D86D800589F68 /* ModalNavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F545E86298D86D800589F68 /* ModalNavigationLink.swift */; };
1F5DF9D82974426300DDAA47 /* AppScreenshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5DF9D72974426300DDAA47 /* AppScreenshot.swift */; };
1F6284D5295209DA0060AAD8 /* AppAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6284D4295209DA0060AAD8 /* AppAction.swift */; };
1F6284D7295218980060AAD8 /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6284D6295218980060AAD8 /* DocumentPicker.swift */; };
@@ -582,6 +585,9 @@
1F180F93298E7A2500D1C98B /* Source+Trusted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+Trusted.swift"; sourceTree = "<group>"; };
1F2EF786297C4D40002FD839 /* LicensesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensesView.swift; sourceTree = "<group>"; };
1F44634429744E570070E514 /* HintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HintView.swift; sourceTree = "<group>"; };
1F545E82298D79E400589F68 /* ErrorLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogView.swift; sourceTree = "<group>"; };
1F545E84298D84CF00589F68 /* FilePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewView.swift; sourceTree = "<group>"; };
1F545E86298D86D800589F68 /* ModalNavigationLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalNavigationLink.swift; sourceTree = "<group>"; };
1F5DF9D72974426300DDAA47 /* AppScreenshot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppScreenshot.swift; sourceTree = "<group>"; };
1F6284D4295209DA0060AAD8 /* AppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAction.swift; sourceTree = "<group>"; };
1F6284D6295218980060AAD8 /* DocumentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPicker.swift; sourceTree = "<group>"; };
@@ -1109,6 +1115,7 @@
1FB96FCE292BBBC9007E68D1 /* SiriShortcutSetupView.swift */,
1F66F5B92938CA5700A910CA /* VisualEffectView.swift */,
1F6284D6295218980060AAD8 /* DocumentPicker.swift */,
1F545E84298D84CF00589F68 /* FilePreviewView.swift */,
);
path = "UIView Representables";
sourceTree = "<group>";
@@ -1193,6 +1200,7 @@
1FAFC5C02927E13C00B8D837 /* SettingsView.swift */,
1F0DD83E29367F6C007608A4 /* ConnectAppleIDView.swift */,
1F2EF786297C4D40002FD839 /* LicensesView.swift */,
1F545E82298D79E400589F68 /* ErrorLogView.swift */,
);
path = Settings;
sourceTree = "<group>";
@@ -1218,6 +1226,7 @@
1FB96FBD292A20E5007E68D1 /* ObservableScrollView.swift */,
1F6E08E529280F4B005059C0 /* RatingStars.swift */,
1F0DD8422936B0F9007608A4 /* RoundedTextField.swift */,
1F545E86298D86D800589F68 /* ModalNavigationLink.swift */,
);
path = "View Components";
sourceTree = "<group>";
@@ -2745,6 +2754,7 @@
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */,
1F6E08DA292806E0005059C0 /* AppRowView.swift in Sources */,
1FB96FC3292A6D7E007E68D1 /* DateFormatterHelper.swift in Sources */,
1F545E87298D86D800589F68 /* ModalNavigationLink.swift in Sources */,
1F943C6D2927F90400ABE095 /* NewsViewModel.swift in Sources */,
1FB96FF3292D0539007E68D1 /* PillButtonProgressViewStyle.swift in Sources */,
1F6E08E829282174005059C0 /* ConfirmAddSourceView.swift in Sources */,
@@ -2812,6 +2822,7 @@
D5F2F6A92720B7C20081CCF5 /* PatchViewController.swift in Sources */,
B39F16132918D7C5002E9404 /* Consts.swift in Sources */,
1F0DD8212933B749007608A4 /* AppPermissionsGrid.swift in Sources */,
1F545E83298D79E400589F68 /* ErrorLogView.swift in Sources */,
1F0DD8452936B3FE007608A4 /* FilledButtonStyle.swift in Sources */,
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */,
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */,
@@ -2848,6 +2859,7 @@
BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */,
1F0DD81C2932D2FF007608A4 /* AppScreenshotsScrollView.swift in Sources */,
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */,
1F545E85298D84CF00589F68 /* FilePreviewView.swift in Sources */,
1FB96FCF292BBBCA007E68D1 /* SiriShortcutSetupView.swift in Sources */,
1F2EF787297C4D40002FD839 /* LicensesView.swift in Sources */,
BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */,

View File

@@ -27,6 +27,14 @@ struct DateFormatterHelper {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
dateFormatter.doesRelativeDateFormatting = true
return dateFormatter
}()
private static let timeFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .short
return dateFormatter
}()
@@ -53,4 +61,12 @@ struct DateFormatterHelper {
static func string(forRelativeDate date: Date, to referenceDate: Date = Date()) -> String {
self.relativeDateFormatter.localizedString(for: date, relativeTo: referenceDate)
}
static func string(for date: Date) -> String {
self.mediumDateFormatter.string(from: date)
}
static func timeString(for date: Date) -> String {
self.timeFormatter.string(from: date)
}
}

View File

@@ -0,0 +1,33 @@
//
// ModalNavigationLink.swift
// SideStore
//
// Created by Fabian Thies on 03.02.23.
// Copyright © 2023 SideStore. All rights reserved.
//
import SwiftUI
struct ModalNavigationLink<Label: View, Modal: View>: View {
let modal: () -> Modal
let label: () -> Label
@State var isPresentingModal: Bool = false
var body: some View {
SwiftUI.Button {
self.isPresentingModal = true
} label: {
self.label()
}
.sheet(isPresented: self.$isPresentingModal) {
self.modal()
}
}
}
//struct ModalNavigationLink_Previews: PreviewProvider {
// static var previews: some View {
// ModalNavigationLink()
// }
//}

View File

@@ -0,0 +1,48 @@
//
// FilePreviewView.swift
// SideStore
//
// Created by Fabian Thies on 03.02.23.
// Copyright © 2023 SideStore. All rights reserved.
//
import SwiftUI
import UIKit
import QuickLook
struct FilePreviewView: UIViewControllerRepresentable {
let urls: [URL]
func makeCoordinator() -> Coordinator {
Coordinator(urls: self.urls)
}
func makeUIViewController(context: Context) -> some UIViewController {
let previewController = QLPreviewController()
previewController.dataSource = context.coordinator
return UINavigationController(rootViewController: previewController)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
context.coordinator.urls = self.urls
}
}
extension FilePreviewView {
class Coordinator: QLPreviewControllerDataSource {
var urls: [URL]
init(urls: [URL]) {
self.urls = urls
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
urls.count
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
urls[index] as QLPreviewItem
}
}
}

View File

@@ -0,0 +1,169 @@
//
// ErrorLogView.swift
// SideStore
//
// Created by Fabian Thies on 03.02.23.
// Copyright © 2023 SideStore. All rights reserved.
//
import SwiftUI
import AltStoreCore
import ExpandableText
struct ErrorLogView: View {
@Environment(\.dismiss) var dismiss
@SwiftUI.FetchRequest(sortDescriptors: [
NSSortDescriptor(keyPath: \LoggedError.date, ascending: false)
])
var loggedErrors: FetchedResults<LoggedError>
var groupedLoggedErrors: [Date: [LoggedError]] {
Dictionary(grouping: loggedErrors, by: { Calendar.current.startOfDay(for: $0.date) })
}
@State var currentFaqUrl: URL?
@State var isShowingMinimuxerLog: Bool = false
@State var isShowingDeleteConfirmation: Bool = false
var body: some View {
List {
ForEach(groupedLoggedErrors.keys.sorted(by: { $0 > $1 }), id: \.self) { date in
Section {
let errors = groupedLoggedErrors[date] ?? []
ForEach(errors, id: \.date) { error in
VStack(spacing: 8) {
HStack(alignment: .top) {
Group {
if let storeApp = error.storeApp {
AppIconView(iconUrl: storeApp.iconURL, size: 50)
} else {
ZStack {
RoundedRectangle(cornerRadius: 50*0.234, style: .continuous)
.foregroundColor(Color(UIColor.secondarySystemFill))
Image(systemSymbol: .exclamationmarkCircle)
.imageScale(.large)
.foregroundColor(.red)
}
.frame(width: 50, height: 50)
}
}
VStack(alignment: .leading) {
Text(error.localizedFailure ?? "Operation Failed")
.bold()
Group {
switch error.domain {
case AltServerErrorDomain: Text("SideServer Error \(error.code)")
case OperationError.domain: Text("SideStore Error \(error.code)")
default: Text(error.error.localizedErrorCode)
}
}
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
Text(DateFormatterHelper.timeString(for: error.date))
.font(.caption)
.foregroundColor(.secondary)
}
let nsError = error.error as NSError
let errorDescription = [nsError.localizedDescription, nsError.localizedRecoverySuggestion].compactMap { $0 }.joined(separator: "\n\n")
Menu {
SwiftUI.Button {
UIPasteboard.general.string = errorDescription
} label: {
Label("Copy Error Message", systemSymbol: .docOnDoc)
}
SwiftUI.Button {
UIPasteboard.general.string = error.error.localizedErrorCode
} label: {
Label("Copy Error Code", systemSymbol: .docOnDoc)
}
SwiftUI.Button {
self.searchFAQ(for: error)
} label: {
Label("Search FAQ", systemSymbol: .magnifyingglass)
}
} label: {
Text(errorDescription)
.multilineTextAlignment(.leading)
.foregroundColor(.primary)
}
}
}
} header: {
Text(DateFormatterHelper.string(for: date))
}
}
}
.navigationBarTitle("Error Log")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
ModalNavigationLink {
FilePreviewView(urls: [
FileManager.default.documentsDirectory.appendingPathComponent("minimuxer.log")
])
.ignoresSafeArea()
} label: {
Image(systemSymbol: .ladybug)
}
SwiftUI.Button {
self.isShowingDeleteConfirmation = true
} label: {
Image(systemSymbol: .trash)
}
.actionSheet(isPresented: self.$isShowingDeleteConfirmation) {
ActionSheet(
title: Text("Are you sure you want to clear the error log?"),
buttons: [
.destructive(Text("Clear Error Log"), action: self.clearLoggedErrors),
.cancel()
]
)
}
}
}
.sheet(item: self.$currentFaqUrl) { url in
SafariView(url: url)
}
}
func searchFAQ(for error: LoggedError) {
let baseURL = URL(string: "https://faq.altstore.io/getting-started/troubleshooting-guide")!
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
let query = [error.domain, "\(error.code)"].joined(separator: "+")
components.queryItems = [URLQueryItem(name: "q", value: query)]
self.currentFaqUrl = components.url ?? baseURL
}
func clearLoggedErrors() {
DatabaseManager.shared.purgeLoggedErrors { result in
if case let .failure(error) = result {
NotificationManager.shared.reportError(error: error)
}
}
}
}
struct ErrorLogView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ErrorLogView()
}
}
}

View File

@@ -154,6 +154,12 @@ struct SettingsView: View {
SwiftUI.Button(action: resetImageCache) {
Text(L10n.SettingsView.resetImageCache)
}
NavigationLink {
ErrorLogView()
} label: {
Text("Show Error Log")
}
} header: {
Text(L10n.SettingsView.debug)
}
@@ -193,7 +199,11 @@ struct SettingsView: View {
func connectAppleID() {
AppManager.shared.authenticate(presentingViewController: nil) { (result) in
guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else {
return
}
AppManager.shared.authenticate(presentingViewController: rootViewController) { (result) in
DispatchQueue.main.async {
switch result
{