2022-09-09 17:44:15 -05:00
//
// E r r o r L o g V i e w C o n t r o l l e r . s w i f t
// A l t S t o r e
//
// C r e a t e d b y R i l e y T e s t u t o n 9 / 6 / 2 2 .
// C o p y r i g h t © 2 0 2 2 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import UIKit
import SafariServices
2023-12-08 18:15:48 -06:00
import QuickLook
2022-09-09 17:44:15 -05:00
import AltStoreCore
import Roxas
import Nuke
2023-01-09 16:17:00 +08:00
import QuickLook
2024-12-29 03:12:59 +05:30
import SwiftUI
2023-01-09 16:17:00 +08:00
2024-12-07 17:45:09 +05:30
final class ErrorLogViewController : UITableViewController , QLPreviewControllerDelegate
2022-09-09 17:44:15 -05:00
{
private lazy var dataSource = self . makeDataSource ( )
private var expandedErrorIDs = Set < NSManagedObjectID > ( )
2024-08-06 10:43:52 +09:00
private var isScrolling = false {
didSet {
guard self . isScrolling != oldValue else { return }
self . updateButtonInteractivity ( )
}
}
2024-12-07 17:45:09 +05:30
2022-09-09 17:44:15 -05:00
private lazy var timeFormatter : DateFormatter = {
let dateFormatter = DateFormatter ( )
dateFormatter . dateStyle = . none
dateFormatter . timeStyle = . short
return dateFormatter
} ( )
2023-10-18 14:26:37 -05:00
@IBOutlet private var exportLogButton : UIBarButtonItem !
@IBOutlet private var clearLogButton : UIBarButtonItem !
2023-12-08 18:15:48 -06:00
private var _exportedLogURL : URL ?
2022-09-09 17:44:15 -05:00
override var preferredStatusBarStyle : UIStatusBarStyle {
return . lightContent
}
override func viewDidLoad ( )
{
super . viewDidLoad ( )
self . tableView . dataSource = self . dataSource
self . tableView . prefetchDataSource = self . dataSource
2023-10-18 14:26:37 -05:00
self . exportLogButton . activityIndicatorView . color = . white
if # unavailable ( iOS 15 )
{
// A s s i g n j u s t c l e a r L o g B u t t o n t o h i d e e x p o r t b u t t o n .
self . navigationItem . rightBarButtonItems = [ self . clearLogButton ]
}
2025-01-02 22:20:37 +05:30
// / / A d j u s t t h e w i d t h o f t h e r i g h t b a r b u t t o n i t e m s
// a d j u s t R i g h t B a r B u t t o n W i d t h ( )
2022-09-09 17:44:15 -05:00
}
2023-01-24 13:56:41 -06:00
2025-01-02 22:20:37 +05:30
// f u n c a d j u s t R i g h t B a r B u t t o n W i d t h ( ) {
// / / A c c e s s t h e c u r r e n t r i g h t B a r B u t t o n I t e m s
// i f l e t r i g h t B a r B u t t o n I t e m s = s e l f . n a v i g a t i o n I t e m . r i g h t B a r B u t t o n I t e m s {
// f o r b a r B u t t o n I t e m i n r i g h t B a r B u t t o n I t e m s {
// / / C h e c k i f t h e b u t t o n i s a s y s t e m b u t t o n , a n d i f s o , r e p l a c e i t w i t h a c u s t o m b u t t o n
// i f b a r B u t t o n I t e m . c u s t o m V i e w = = n i l {
// / / R e p l a c e w i t h a c u s t o m U I B u t t o n f o r e a c h b a r b u t t o n i t e m
// l e t c u s t o m B u t t o n = U I B u t t o n ( t y p e : . c u s t o m )
// i f l e t i m a g e = b a r B u t t o n I t e m . i m a g e {
// c u s t o m B u t t o n . s e t I m a g e ( i m a g e , f o r : . n o r m a l )
// }
// i f l e t a c t i o n = b a r B u t t o n I t e m . a c t i o n {
// c u s t o m B u t t o n . a d d T a r g e t ( b a r B u t t o n I t e m . t a r g e t , a c t i o n : a c t i o n , f o r : . t o u c h U p I n s i d e )
// }
//
// / / C a l c u l a t e t h e o r i g i n a l s i z e b a s e d o n t h e s y s t e m b u t t o n
// l e t o r i g i n a l S i z e = c u s t o m B u t t o n . s i z e T h a t F i t s ( C G S i z e ( w i d t h : C G F l o a t . g r e a t e s t F i n i t e M a g n i t u d e , h e i g h t : C G F l o a t . g r e a t e s t F i n i t e M a g n i t u d e ) )
//
// l e t s c a l e F a c t o r = 0 . 7
//
// / / S c a l e t h e s i z e b y 0 . 7 ( 7 0 % )
// l e t s c a l e d S i z e = C G S i z e ( w i d t h : o r i g i n a l S i z e . w i d t h * s c a l e F a c t o r , h e i g h t : o r i g i n a l S i z e . h e i g h t * s c a l e F a c t o r )
//
// / / A d j u s t t h e c u s t o m b u t t o n ' s w i d t h
// / / c u s t o m B u t t o n . f r a m e . s i z e = C G S i z e ( w i d t h : 2 2 , h e i g h t : 2 2 ) / / A d j u s t w i d t h a s n e e d e d
// c u s t o m B u t t o n . f r a m e . s i z e = s c a l e d S i z e / / A d j u s t w i d t h a s n e e d e d
//
// / / S e t t h e c u s t o m b u t t o n a s t h e c u s t o m v i e w f o r t h e U I B a r B u t t o n I t e m
// b a r B u t t o n I t e m . c u s t o m V i e w = c u s t o m B u t t o n
// }
// }
// }
// }
2023-01-24 13:56:41 -06:00
override func prepare ( for segue : UIStoryboardSegue , sender : Any ? )
{
2024-08-06 10:43:52 +09:00
guard let loggedError = sender as ? LoggedError , segue . identifier = = " showErrorDetails " else { return }
2023-01-24 13:56:41 -06:00
2024-08-06 10:43:52 +09:00
let navigationController = segue . destination as ! UINavigationController
2023-01-24 13:56:41 -06:00
2024-08-06 10:43:52 +09:00
let errorDetailsViewController = navigationController . viewControllers . first as ! ErrorDetailsViewController
errorDetailsViewController . loggedError = loggedError
}
2023-01-24 13:56:41 -06:00
@IBAction private func unwindFromErrorDetails ( _ segue : UIStoryboardSegue )
{
}
2022-09-09 17:44:15 -05:00
}
private extension ErrorLogViewController
{
func makeDataSource ( ) -> RSTFetchedResultsTableViewPrefetchingDataSource < LoggedError , UIImage >
{
let fetchRequest = LoggedError . fetchRequest ( ) as NSFetchRequest < LoggedError >
fetchRequest . sortDescriptors = [ NSSortDescriptor ( keyPath : \ LoggedError . date , ascending : false ) ]
fetchRequest . returnsObjectsAsFaults = false
let fetchedResultsController = NSFetchedResultsController ( fetchRequest : fetchRequest , managedObjectContext : DatabaseManager . shared . viewContext , sectionNameKeyPath : # keyPath ( LoggedError . localizedDateString ) , cacheName : nil )
let dataSource = RSTFetchedResultsTableViewPrefetchingDataSource < LoggedError , UIImage > ( fetchedResultsController : fetchedResultsController )
dataSource . proxy = self
dataSource . rowAnimation = . fade
dataSource . cellConfigurationHandler = { [ weak self ] ( cell , loggedError , indexPath ) in
guard let self else { return }
let cell = cell as ! ErrorLogTableViewCell
cell . dateLabel . text = self . timeFormatter . string ( from : loggedError . date )
cell . errorFailureLabel . text = loggedError . localizedFailure ? ? NSLocalizedString ( " Operation Failed " , comment : " " )
2023-01-24 13:56:41 -06:00
cell . errorCodeLabel . text = loggedError . error . localizedErrorCode
2022-09-09 17:44:15 -05:00
2022-09-09 17:44:15 -05:00
let nsError = loggedError . error as NSError
let errorDescription = [ nsError . localizedDescription , nsError . localizedRecoverySuggestion ] . compactMap { $0 } . joined ( separator : " \n \n " )
cell . errorDescriptionTextView . text = errorDescription
cell . errorDescriptionTextView . maximumNumberOfLines = 5
cell . errorDescriptionTextView . isCollapsed = ! self . expandedErrorIDs . contains ( loggedError . objectID )
cell . errorDescriptionTextView . moreButton . addTarget ( self , action : #selector ( ErrorLogViewController . toggleCollapsingCell ( _ : ) ) , for : . primaryActionTriggered )
cell . appIconImageView . image = nil
cell . appIconImageView . isIndicatingActivity = true
cell . appIconImageView . layer . borderColor = UIColor . gray . cgColor
let displayScale = ( self . traitCollection . displayScale = = 0.0 ) ? 1.0 : self . traitCollection . displayScale // 0 . 0 = = " u n s p e c i f i e d "
cell . appIconImageView . layer . borderWidth = 1.0 / displayScale
2023-03-02 16:53:36 -06:00
let menu = UIMenu ( title : " " , children : [
UIAction ( title : NSLocalizedString ( " Copy Error Message " , comment : " " ) , image : UIImage ( systemName : " doc.on.doc " ) ) { [ weak self ] _ in
self ? . copyErrorMessage ( for : loggedError )
} ,
UIAction ( title : NSLocalizedString ( " Copy Error Code " , comment : " " ) , image : UIImage ( systemName : " doc.on.doc " ) ) { [ weak self ] _ in
self ? . copyErrorCode ( for : loggedError )
} ,
UIAction ( title : NSLocalizedString ( " Search FAQ " , comment : " " ) , image : UIImage ( systemName : " magnifyingglass " ) ) { [ weak self ] _ in
self ? . searchFAQ ( for : loggedError )
} ,
UIAction ( title : NSLocalizedString ( " View More Details " , comment : " " ) , image : UIImage ( systemName : " ellipsis.circle " ) ) { [ weak self ] _ in
self ? . viewMoreDetails ( for : loggedError )
} ,
] )
2022-09-09 17:44:15 -05:00
cell . menuButton . menu = menu
2024-12-07 17:45:09 +05:30
if self . isScrolling
{
cell . menuButton . showsMenuAsPrimaryAction = false
2022-09-09 17:44:15 -05:00
}
2024-12-07 17:45:09 +05:30
else
{
cell . menuButton . showsMenuAsPrimaryAction = true
}
cell . selectionStyle = . none
2022-09-09 17:44:15 -05:00
// I n c l u d e e r r o r D e s c r i p t i o n T e x t V i e w ' s t e x t i n c e l l s u m m a r y .
cell . accessibilityLabel = [ cell . errorFailureLabel . text , cell . dateLabel . text , cell . errorCodeLabel . text , cell . errorDescriptionTextView . text ] . compactMap { $0 } . joined ( separator : " . " )
// G r o u p a l l p a r a g r a p h s t o g e t h e r i n t o s i n g l e a c c e s s i b i l i t y e l e m e n t ( o t h e r w i s e , e a c h p a r a g r a p h i s i n d e p e n d e n t l y s e l e c t a b l e ) .
cell . errorDescriptionTextView . accessibilityLabel = cell . errorDescriptionTextView . text
}
dataSource . prefetchHandler = { ( loggedError , indexPath , completion ) in
RSTAsyncBlockOperation { ( operation ) in
loggedError . managedObjectContext ? . perform {
if let installedApp = loggedError . installedApp
{
installedApp . loadIcon { ( result ) in
switch result
{
case . failure ( let error ) : completion ( nil , error )
case . success ( let image ) : completion ( image , nil )
}
}
}
else if let storeApp = loggedError . storeApp
{
2023-03-01 15:00:27 -06:00
ImagePipeline . shared . loadImage ( with : storeApp . iconURL , progress : nil ) { result in
2022-09-09 17:44:15 -05:00
guard ! operation . isCancelled else { return operation . finish ( ) }
2023-03-01 15:00:27 -06:00
switch result
2022-09-09 17:44:15 -05:00
{
2023-03-01 15:00:27 -06:00
case . success ( let response ) : completion ( response . image , nil )
case . failure ( let error ) : completion ( nil , error )
2022-09-09 17:44:15 -05:00
}
}
}
else
{
2022-11-16 18:25:55 -06:00
// I n s t a l l e d A p p w a s p r o b a b l y d e l e t e d .
2022-09-09 17:44:15 -05:00
completion ( nil , nil )
}
}
}
}
dataSource . prefetchCompletionHandler = { ( cell , image , indexPath , error ) in
let cell = cell as ! ErrorLogTableViewCell
cell . appIconImageView . image = image
cell . appIconImageView . isIndicatingActivity = false
}
let placeholderView = RSTPlaceholderView ( )
placeholderView . textLabel . text = NSLocalizedString ( " No Errors " , comment : " " )
placeholderView . detailTextLabel . text = NSLocalizedString ( " Errors that occur when sideloading or refreshing apps will appear here. " , comment : " " )
dataSource . placeholderView = placeholderView
return dataSource
}
}
private extension ErrorLogViewController
{
@IBAction func toggleCollapsingCell ( _ sender : UIButton )
{
let point = self . tableView . convert ( sender . center , from : sender . superview )
guard let indexPath = self . tableView . indexPathForRow ( at : point ) , let cell = self . tableView . cellForRow ( at : indexPath ) as ? ErrorLogTableViewCell else { return }
let loggedError = self . dataSource . item ( at : indexPath )
if cell . errorDescriptionTextView . isCollapsed
{
self . expandedErrorIDs . remove ( loggedError . objectID )
}
else
{
self . expandedErrorIDs . insert ( loggedError . objectID )
}
self . tableView . performBatchUpdates {
cell . layoutIfNeeded ( )
}
}
2024-12-29 03:12:59 +05:30
enum LogView : String {
case consoleLog = " console-log "
case minimuxerLog = " minimuxer-log "
// T h i s c l a s s w i l l m a n a g e t h e Q L P r e v i e w C o n t r o l l e r a n d t h e t i m e r .
private class LogViewManager {
var previewController : QLPreviewController
var refreshTimer : Timer ?
var logView : LogView
init ( previewController : QLPreviewController , logView : LogView ) {
self . previewController = previewController
self . logView = logView
}
// S t a r t r e f r e s h i n g t h e p r e v i e w c o n t r o l l e r e v e r y s e c o n d
func startRefreshing ( ) {
refreshTimer = Timer . scheduledTimer ( timeInterval : 1.0 , target : self , selector : #selector ( refreshPreview ) , userInfo : nil , repeats : true )
}
@objc private func refreshPreview ( ) {
previewController . reloadData ( )
}
// S t o p t h e t i m e r t o p r e v e n t l e a k s
func stopRefreshing ( ) {
refreshTimer ? . invalidate ( )
refreshTimer = nil
}
func updateLogPath ( ) {
// F o r c e t h e Q L P r e v i e w C o n t r o l l e r t o r e l o a d b y c h a n g i n g t h e f i l e p a t h
previewController . reloadData ( )
}
}
// M e t h o d t o g e t t h e Q L P r e v i e w C o n t r o l l e r f o r t h i s l o g t y p e
func getViewController ( _ dataSource : QLPreviewControllerDataSource ) -> QLPreviewController {
let previewController = QLPreviewController ( )
previewController . restorationIdentifier = self . rawValue
previewController . dataSource = dataSource
// C r e a t e L o g V i e w M a n a g e r a n d s t a r t r e f r e s h i n g
let manager = LogViewManager ( previewController : previewController , logView : self )
2025-01-02 22:20:37 +05:30
// m a n a g e r . s t a r t R e f r e s h i n g ( ) / / D O N O T R E F R E S H t h e f u l l v i e w c o n t e n t s c a u s i n g f l i c k e r i n g
2024-12-29 03:12:59 +05:30
return previewController
}
func getLogPath ( ) -> URL {
switch self {
case . consoleLog :
let appDelegate = UIApplication . shared . delegate as ! AppDelegate
return appDelegate . consoleLog . logFileURL
case . minimuxerLog :
return FileManager . default . documentsDirectory . appendingPathComponent ( " minimuxer.log " )
}
}
2023-01-09 16:17:00 +08:00
}
2025-01-02 22:20:37 +05:30
@IBAction func showConsoleLogs ( _ sender : UIBarButtonItem ) {
2024-12-29 03:12:59 +05:30
// C r e a t e t h e S w i f t U I C o n s o l e L o g V i e w w i t h t h e U R L
let consoleLogView = ConsoleLogView ( logURL : ( UIApplication . shared . delegate as ! AppDelegate ) . consoleLog . logFileURL )
// C r e a t e t h e U I H o s t i n g C o n t r o l l e r
let consoleLogController = UIHostingController ( rootView : consoleLogView )
// C o n f i g u r e t h e b o t t o m s h e e t p r e s e n t a t i o n
consoleLogController . modalPresentationStyle = . pageSheet
if let sheet = consoleLogController . sheetPresentationController {
sheet . detents = [ . medium ( ) , . large ( ) ] // Y o u c a n a d j u s t t h e s i z e o f t h e s h e e t ( m e d i u m / l a r g e )
sheet . prefersGrabberVisible = true // O p t i o n a l : S h o w s a g r a b b e r a t t h e t o p o f t h e s h e e t
sheet . selectedDetentIdentifier = . large // D e f a u l t s i z e w h e n p r e s e n t e d
}
// P r e s e n t t h e b o t t o m s h e e t
present ( consoleLogController , animated : true , completion : nil )
}
2022-09-09 17:44:15 -05:00
@IBAction func clearLoggedErrors ( _ sender : UIBarButtonItem )
{
let alertController = UIAlertController ( title : NSLocalizedString ( " Are you sure you want to clear the error log? " , comment : " " ) , message : nil , preferredStyle : . actionSheet )
alertController . popoverPresentationController ? . barButtonItem = sender
alertController . addAction ( . cancel )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Clear Error Log " , comment : " " ) , style : . destructive ) { _ in
self . clearLoggedErrors ( )
} )
self . present ( alertController , animated : true )
}
func clearLoggedErrors ( )
{
DatabaseManager . shared . purgeLoggedErrors { result in
do
{
try result . get ( )
}
catch
{
DispatchQueue . main . async {
let alertController = UIAlertController ( title : NSLocalizedString ( " Failed to Clear Error Log " , comment : " " ) , message : error . localizedDescription , preferredStyle : . alert )
alertController . addAction ( . ok )
self . present ( alertController , animated : true )
}
}
}
}
func copyErrorMessage ( for loggedError : LoggedError )
{
let nsError = loggedError . error as NSError
let errorMessage = [ nsError . localizedDescription , nsError . localizedRecoverySuggestion ] . compactMap { $0 } . joined ( separator : " \n \n " )
UIPasteboard . general . string = errorMessage
}
func copyErrorCode ( for loggedError : LoggedError )
{
let errorCode = loggedError . error . localizedErrorCode
UIPasteboard . general . string = errorCode
}
func searchFAQ ( for loggedError : LoggedError )
{
2025-07-02 18:19:36 -06:00
let staticURL = URL ( string : " https://docs.sidestore.io/docs/troubleshooting/error-codes " ) !
let safariViewController = SFSafariViewController ( url : staticURL )
2022-09-09 17:44:15 -05:00
safariViewController . preferredControlTintColor = . altPrimary
self . present ( safariViewController , animated : true )
}
2024-08-06 10:43:52 +09:00
func viewMoreDetails ( for loggedError : LoggedError ) {
self . performSegue ( withIdentifier : " showErrorDetails " , sender : loggedError )
}
2023-10-18 14:26:37 -05:00
2025-01-02 22:20:37 +05:30
@IBAction func showMinimuxerLogs ( _ sender : UIBarButtonItem )
2023-10-18 14:26:37 -05:00
{
2025-01-02 22:20:37 +05:30
// S h o w m i n i m u x e r . l o g
let previewController = LogView . minimuxerLog . getViewController ( self )
let navigationController = UINavigationController ( rootViewController : previewController )
present ( navigationController , animated : true , completion : nil )
}
// @ a v a i l a b l e ( i O S 1 5 , * )
// @ I B A c t i o n f u n c e x p o r t D e t a i l e d L o g ( _ s e n d e r : U I B a r B u t t o n I t e m )
// {
// s e l f . e x p o r t L o g B u t t o n . i s I n d i c a t i n g A c t i v i t y = t r u e
//
// T a s k < V o i d , N e v e r > . d e t a c h e d ( p r i o r i t y : . u s e r I n i t i a t e d ) {
// d o
// {
// l e t s t o r e = t r y O S L o g S t o r e ( s c o p e : . c u r r e n t P r o c e s s I d e n t i f i e r )
2024-12-29 03:12:59 +05:30
//
2025-01-02 22:20:37 +05:30
// / / A l l l o g s s i n c e t h e a p p l a u n c h e d .
// l e t p o s i t i o n = s t o r e . p o s i t i o n ( t i m e I n t e r v a l S i n c e L a t e s t B o o t : 0 )
// / / l e t p r e d i c a t e = N S P r e d i c a t e ( f o r m a t : " s u b s y s t e m = = % @ " , L o g g e r . a l t s t o r e S u b s y s t e m )
// / /
// / / l e t e n t r i e s = t r y s t o r e . g e t E n t r i e s ( a t : p o s i t i o n , m a t c h i n g : p r e d i c a t e )
// / / . c o m p a c t M a p { $ 0 a s ? O S L o g E n t r y L o g }
// / / . m a p { " [ \ ( $ 0 . d a t e . f o r m a t t e d ( ) ) ] [ \ ( $ 0 . c a t e g o r y ) ] [ \ ( $ 0 . l e v e l . l o c a l i z e d N a m e ) ] \ ( $ 0 . c o m p o s e d M e s s a g e ) " }
// / /
// / / R e m o v e t h e p r e d i c a t e t o g e t a l l l o g e n t r i e s
// / / l e t e n t r i e s = t r y s t o r e . g e t E n t r i e s ( a t : p o s i t i o n )
// / / . c o m p a c t M a p { $ 0 a s ? O S L o g E n t r y L o g }
// / / . m a p { " [ \ ( $ 0 . d a t e . f o r m a t t e d ( ) ) ] [ \ ( $ 0 . c a t e g o r y ) ] [ \ ( $ 0 . l e v e l . l o c a l i z e d N a m e ) ] \ ( $ 0 . c o m p o s e d M e s s a g e ) " }
2024-12-29 03:12:59 +05:30
//
// l e t e n t r i e s = t r y s t o r e . g e t E n t r i e s ( a t : p o s i t i o n )
2025-01-02 22:20:37 +05:30
//
// / / l e t o u t p u t T e x t = e n t r i e s . j o i n e d ( s e p a r a t o r : " \ n " )
// l e t o u t p u t T e x t = e n t r i e s . m a p { $ 0 . d e s c r i p t i o n } . j o i n e d ( s e p a r a t o r : " \ n " )
//
// l e t o u t p u t D i r e c t o r y = F i l e M a n a g e r . d e f a u l t . u n i q u e T e m p o r a r y U R L ( )
// t r y F i l e M a n a g e r . d e f a u l t . c r e a t e D i r e c t o r y ( a t : o u t p u t D i r e c t o r y , w i t h I n t e r m e d i a t e D i r e c t o r i e s : t r u e )
//
// l e t o u t p u t U R L = o u t p u t D i r e c t o r y . a p p e n d i n g P a t h C o m p o n e n t ( " a l t s t o r e . l o g " )
// t r y o u t p u t T e x t . w r i t e ( t o : o u t p u t U R L , a t o m i c a l l y : t r u e , e n c o d i n g : . u t f 8 )
//
// a w a i t M a i n A c t o r . r u n {
// s e l f . _ e x p o r t e d L o g U R L = o u t p u t U R L
//
// l e t p r e v i e w C o n t r o l l e r = Q L P r e v i e w C o n t r o l l e r ( )
// p r e v i e w C o n t r o l l e r . d e l e g a t e = s e l f
// p r e v i e w C o n t r o l l e r . d a t a S o u r c e = s e l f
// p r e v i e w C o n t r o l l e r . v i e w . t i n t C o l o r = . a l t P r i m a r y
// s e l f . p r e s e n t ( p r e v i e w C o n t r o l l e r , a n i m a t e d : t r u e )
// }
// }
// c a t c h
// {
// L o g g e r . m a i n . e r r o r ( " F a i l e d t o e x p o r t O S L o g e n t r i e s . \ ( e r r o r . l o c a l i z e d D e s c r i p t i o n , p r i v a c y : . p u b l i c ) " )
//
// a w a i t M a i n A c t o r . r u n {
// l e t a l e r t C o n t r o l l e r = U I A l e r t C o n t r o l l e r ( t i t l e : N S L o c a l i z e d S t r i n g ( " U n a b l e t o E x p o r t D e t a i l e d L o g " , c o m m e n t : " " ) , m e s s a g e : e r r o r . l o c a l i z e d D e s c r i p t i o n , p r e f e r r e d S t y l e : . a l e r t )
// a l e r t C o n t r o l l e r . a d d A c t i o n ( . o k )
// s e l f . p r e s e n t ( a l e r t C o n t r o l l e r , a n i m a t e d : t r u e )
// }
// }
//
// a w a i t M a i n A c t o r . r u n {
// s e l f . e x p o r t L o g B u t t o n . i s I n d i c a t i n g A c t i v i t y = f a l s e
// }
// }
// }
2022-09-09 17:44:15 -05:00
}
extension ErrorLogViewController
{
override func tableView ( _ tableView : UITableView , didSelectRowAt indexPath : IndexPath )
{
2024-08-06 10:43:52 +09:00
guard # unavailable ( iOS 14 ) else { return }
2022-09-09 17:44:15 -05:00
let loggedError = self . dataSource . item ( at : indexPath )
let alertController = UIAlertController ( title : nil , message : nil , preferredStyle : . actionSheet )
alertController . addAction ( UIAlertAction ( title : UIAlertAction . cancel . title , style : UIAlertAction . cancel . style ) { _ in
tableView . deselectRow ( at : indexPath , animated : true )
} )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Copy Error Message " , comment : " " ) , style : . default ) { [ weak self ] _ in
self ? . copyErrorMessage ( for : loggedError )
tableView . deselectRow ( at : indexPath , animated : true )
} )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Copy Error Code " , comment : " " ) , style : . default ) { [ weak self ] _ in
self ? . copyErrorCode ( for : loggedError )
tableView . deselectRow ( at : indexPath , animated : true )
} )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Search FAQ " , comment : " " ) , style : . default ) { [ weak self ] _ in
self ? . searchFAQ ( for : loggedError )
tableView . deselectRow ( at : indexPath , animated : true )
} )
self . present ( alertController , animated : true )
}
override func tableView ( _ tableView : UITableView , trailingSwipeActionsConfigurationForRowAt indexPath : IndexPath ) -> UISwipeActionsConfiguration ?
{
let deleteAction = UIContextualAction ( style : . destructive , title : NSLocalizedString ( " Delete " , comment : " " ) ) { _ , _ , completion in
let loggedError = self . dataSource . item ( at : indexPath )
DatabaseManager . shared . persistentContainer . performBackgroundTask { context in
do
{
let loggedError = context . object ( with : loggedError . objectID ) as ! LoggedError
context . delete ( loggedError )
try context . save ( )
completion ( true )
}
catch
{
print ( " [ALTLog] Failed to delete LoggedError \( loggedError . objectID ) : " , error )
completion ( false )
}
}
}
let configuration = UISwipeActionsConfiguration ( actions : [ deleteAction ] )
configuration . performsFirstActionWithFullSwipe = false
return configuration
}
override func tableView ( _ tableView : UITableView , titleForHeaderInSection section : Int ) -> String ?
{
let indexPath = IndexPath ( row : 0 , section : section )
let loggedError = self . dataSource . item ( at : indexPath )
2022-09-27 15:41:41 -05:00
if Calendar . current . isDateInToday ( loggedError . date )
{
return NSLocalizedString ( " Today " , comment : " " )
}
else
{
return loggedError . localizedDateString
}
2022-09-09 17:44:15 -05:00
}
}
2023-01-09 16:17:00 +08:00
extension ErrorLogViewController : QLPreviewControllerDataSource {
func numberOfPreviewItems ( in controller : QLPreviewController ) -> Int {
return 1
}
2024-12-29 03:12:59 +05:30
func previewController ( _ controller : QLPreviewController , previewItemAt index : Int ) -> QLPreviewItem
{
guard let identifier = controller . restorationIdentifier ,
let logView = LogView ( rawValue : identifier ) else {
fatalError ( " Invalid restorationIdentifier " )
}
return logView . getLogPath ( ) as QLPreviewItem
2023-01-09 16:17:00 +08:00
}
}
2024-08-06 10:43:52 +09:00
extension ErrorLogViewController
{
override func scrollViewWillBeginDragging ( _ scrollView : UIScrollView )
{
self . isScrolling = true
}
override func scrollViewDidEndDecelerating ( _ scrollView : UIScrollView )
{
self . isScrolling = false
}
override func scrollViewDidEndDragging ( _ scrollView : UIScrollView , willDecelerate decelerate : Bool )
{
guard ! decelerate else { return }
self . isScrolling = false
}
private func updateButtonInteractivity ( )
{
for case let cell as ErrorLogTableViewCell in self . tableView . visibleCells
{
2024-12-07 17:45:09 +05:30
if self . isScrolling
{
cell . menuButton . showsMenuAsPrimaryAction = false
}
else
{
cell . menuButton . showsMenuAsPrimaryAction = true
}
2024-08-06 10:43:52 +09:00
}
}
}
2023-12-08 18:15:48 -06:00
2024-12-07 17:45:09 +05:30
// e x t e n s i o n E r r o r L o g V i e w C o n t r o l l e r : Q L P r e v i e w C o n t r o l l e r D a t a S o u r c e , Q L P r e v i e w C o n t r o l l e r D e l e g a t e
// {
// f u n c n u m b e r O f P r e v i e w I t e m s ( i n c o n t r o l l e r : Q L P r e v i e w C o n t r o l l e r ) - > I n t
// {
// r e t u r n 1
// }
//
// f u n c p r e v i e w C o n t r o l l e r ( _ c o n t r o l l e r : Q L P r e v i e w C o n t r o l l e r , p r e v i e w I t e m A t i n d e x : I n t ) - > Q L P r e v i e w I t e m
// {
// r e t u r n ( _ e x p o r t e d L o g U R L a s ? N S U R L ) ? ? N S U R L ( )
// }
//
// f u n c p r e v i e w C o n t r o l l e r D i d D i s m i s s ( _ c o n t r o l l e r : Q L P r e v i e w C o n t r o l l e r )
// {
// g u a r d l e t e x p o r t e d L o g U R L = _ e x p o r t e d L o g U R L e l s e { r e t u r n }
//
// l e t p a r e n t D i r e c t o r y = e x p o r t e d L o g U R L . d e l e t i n g L a s t P a t h C o m p o n e n t ( )
//
// d o
// {
// t r y F i l e M a n a g e r . d e f a u l t . r e m o v e I t e m ( a t : p a r e n t D i r e c t o r y )
// }
// c a t c h
// {
// L o g g e r . m a i n . e r r o r ( " F a i l e d t o r e m o v e t e m p o r a r y l o g d i r e c t o r y \ ( p a r e n t D i r e c t o r y . l a s t P a t h C o m p o n e n t , p r i v a c y : . p u b l i c ) . \ ( e r r o r . l o c a l i z e d D e s c r i p t i o n , p r i v a c y : . p u b l i c ) " )
// }
//
// _ e x p o r t e d L o g U R L = n i l
// }
// }