mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
[ADD] Error log view
This commit is contained in:
@@ -35,6 +35,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 */; };
|
||||
@@ -580,6 +583,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>"; };
|
||||
@@ -1108,6 +1114,7 @@
|
||||
1FB96FCE292BBBC9007E68D1 /* SiriShortcutSetupView.swift */,
|
||||
1F66F5B92938CA5700A910CA /* VisualEffectView.swift */,
|
||||
1F6284D6295218980060AAD8 /* DocumentPicker.swift */,
|
||||
1F545E84298D84CF00589F68 /* FilePreviewView.swift */,
|
||||
);
|
||||
path = "UIView Representables";
|
||||
sourceTree = "<group>";
|
||||
@@ -1192,6 +1199,7 @@
|
||||
1FAFC5C02927E13C00B8D837 /* SettingsView.swift */,
|
||||
1F0DD83E29367F6C007608A4 /* ConnectAppleIDView.swift */,
|
||||
1F2EF786297C4D40002FD839 /* LicensesView.swift */,
|
||||
1F545E82298D79E400589F68 /* ErrorLogView.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1217,6 +1225,7 @@
|
||||
1FB96FBD292A20E5007E68D1 /* ObservableScrollView.swift */,
|
||||
1F6E08E529280F4B005059C0 /* RatingStars.swift */,
|
||||
1F0DD8422936B0F9007608A4 /* RoundedTextField.swift */,
|
||||
1F545E86298D86D800589F68 /* ModalNavigationLink.swift */,
|
||||
);
|
||||
path = "View Components";
|
||||
sourceTree = "<group>";
|
||||
@@ -2769,6 +2778,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 */,
|
||||
@@ -2836,6 +2846,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 */,
|
||||
@@ -2873,6 +2884,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 */,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
33
AltStore/View Components/ModalNavigationLink.swift
Normal file
33
AltStore/View Components/ModalNavigationLink.swift
Normal 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()
|
||||
// }
|
||||
//}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
169
AltStore/Views/Settings/ErrorLogView.swift
Normal file
169
AltStore/Views/Settings/ErrorLogView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user