24
24
#import " ARKEmailBugReportConfiguration.h"
25
25
#import " ARKEmailBugReportConfiguration_Protected.h"
26
26
27
- #import < Aardvark/Aardvark-Swift.h>
28
-
29
27
NSString *const ARKScreenshotFlashAnimationKey = @" ScreenshotFlashAnimation" ;
30
28
31
29
@@ -63,25 +61,25 @@ @implementation ARKEmailBugReporter
63
61
- (instancetype )initWithEmailAddress : (NSString *)emailAddress logStore : (ARKLogStore *)logStore ;
64
62
{
65
63
self = [super init ];
66
-
64
+
67
65
_prefilledEmailBody = [NSString stringWithFormat: @" Reproduction Steps:\n "
68
66
@" 1. \n "
69
67
@" 2. \n "
70
68
@" 3. \n "
71
69
@" \n "
72
70
@" System: %@ " , [[NSProcessInfo processInfo ] operatingSystemVersionString ]];
73
-
71
+
74
72
_logFormatter = [ARKDefaultLogFormatter new ];
75
73
_numberOfRecentErrorLogsToIncludeInEmailBodyWhenAttachmentsAreAvailable = 3 ;
76
74
_numberOfRecentErrorLogsToIncludeInEmailBodyWhenAttachmentsAreUnavailable = 15 ;
77
75
_emailComposeWindowLevel = UIWindowLevelStatusBar + 3.0 ;
78
76
_attachesViewHierarchyDescription = YES ;
79
-
77
+
80
78
_mutableLogStores = [NSMutableArray new ];
81
-
79
+
82
80
_bugReportRecipientEmailAddress = [emailAddress copy ];
83
81
[self addLogStores: @[logStore]];
84
-
82
+
85
83
return self;
86
84
}
87
85
@@ -101,7 +99,7 @@ - (void)composeBugReportWithScreenshot:(BOOL)attachScreenshot;
101
99
{
102
100
ARKCheckCondition (self.bugReportRecipientEmailAddress .length > 0 , , @" Attempting to compose a bug report without a recipient email address." );
103
101
ARKCheckCondition (self.mutableLogStores .count > 0 , , @" Attempting to compose a bug report without logs." );
104
-
102
+
105
103
self.attachScreenshotToNextBugReport = attachScreenshot;
106
104
107
105
if (self.attachesViewHierarchyDescription ) {
@@ -111,7 +109,7 @@ - (void)composeBugReportWithScreenshot:(BOOL)attachScreenshot;
111
109
if (attachScreenshot && !self.screenFlashView ) {
112
110
// Take a screenshot.
113
111
ARKLogScreenshot ();
114
-
112
+
115
113
// Flash the screen to simulate a screenshot being taken.
116
114
UIWindow *keyWindow = [ARKEmailBugReporter _keyWindow ];
117
115
if (keyWindow == nil ) {
@@ -122,13 +120,13 @@ - (void)composeBugReportWithScreenshot:(BOOL)attachScreenshot;
122
120
self.screenFlashView .layer .opacity = 0 .0f ;
123
121
self.screenFlashView .layer .backgroundColor = [[UIColor whiteColor ] CGColor ];
124
122
[keyWindow addSubview: self .screenFlashView];
125
-
123
+
126
124
CAKeyframeAnimation *screenFlash = [CAKeyframeAnimation animationWithKeyPath: @" opacity" ];
127
125
screenFlash.duration = 0.8 ;
128
126
screenFlash.values = @[@0.0 , @0.8 , @1.0 , @0.9 , @0.8 , @0.7 , @0.6 , @0.5 , @0.4 , @0.3 , @0.2 , @0.1 , @0.0 ];
129
127
screenFlash.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear ];
130
128
screenFlash.delegate = self;
131
-
129
+
132
130
// Start the screen flash animation. Once this is done we'll fire up the bug reporter.
133
131
[self .screenFlashView.layer addAnimation: screenFlash forKey: ARKScreenshotFlashAnimationKey];
134
132
}
@@ -140,25 +138,25 @@ - (void)composeBugReportWithScreenshot:(BOOL)attachScreenshot;
140
138
- (void )addLogStores : (NSArray *)logStores ;
141
139
{
142
140
ARKCheckCondition (self.mailComposeViewController == nil , , @" Can not add a log store while a bug is being composed." );
143
-
141
+
144
142
for (ARKLogStore *logStore in logStores) {
145
143
ARKCheckCondition ([logStore isKindOfClass: [ARKLogStore class ]], , @" Can not add a log store of class %@ " , NSStringFromClass ([logStore class ]));
146
144
if ([self .mutableLogStores containsObject: logStore]) {
147
145
[self .mutableLogStores removeObject: logStore];
148
146
}
149
-
147
+
150
148
[self .mutableLogStores addObject: logStore];
151
149
}
152
150
}
153
151
154
152
- (void )removeLogStores : (NSArray *)logStores ;
155
153
{
156
154
ARKCheckCondition (self.mailComposeViewController == nil , , @" Can not add a remove a controller while a bug is being composed." );
157
-
155
+
158
156
for (ARKLogStore *logStore in logStores) {
159
157
ARKCheckCondition ([logStore isKindOfClass: [ARKLogStore class ]], , @" Can not remove a log store of class %@ " , NSStringFromClass ([logStore class ]));
160
158
}
161
-
159
+
162
160
for (ARKLogStore *logStore in logStores) {
163
161
[self .mutableLogStores removeObject: logStore];
164
162
}
@@ -195,7 +193,7 @@ - (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished;
195
193
{
196
194
[self .screenFlashView removeFromSuperview ];
197
195
self.screenFlashView = nil ;
198
-
196
+
199
197
[self _showBugReportPrompt ];
200
198
}
201
199
@@ -222,7 +220,7 @@ - (UIWindow *)emailComposeWindow;
222
220
_emailComposeWindow = [[UIWindow alloc ] initWithFrame: [[UIScreen mainScreen ] bounds ]];
223
221
_emailComposeWindow.windowLevel = self.emailComposeWindowLevel ;
224
222
}
225
-
223
+
226
224
return _emailComposeWindow;
227
225
}
228
226
@@ -243,7 +241,7 @@ - (ARKEmailBugReportConfiguration *)_configurationWithCurrentSettings;
243
241
{
244
242
ARKEmailBugReportConfiguration *const configuration = [[ARKEmailBugReportConfiguration alloc ] initWithScreenshot: self .attachScreenshotToNextBugReport
245
243
viewHierarchyDescription: self .attachesViewHierarchyDescription];
246
-
244
+
247
245
if (self.emailAttachmentAdditionsDelegate != nil ) {
248
246
NSMutableArray *const filteredLogStores = [NSMutableArray arrayWithCapacity: self .logStores.count];
249
247
for (ARKLogStore *logStore in self.logStores ) {
@@ -252,13 +250,13 @@ - (ARKEmailBugReportConfiguration *)_configurationWithCurrentSettings;
252
250
}
253
251
}
254
252
configuration.logStores = filteredLogStores;
255
-
253
+
256
254
configuration.additionalAttachments = [self .emailAttachmentAdditionsDelegate additionalEmailAttachmentsForEmailBugReporter: self ] ?: @[];
257
-
255
+
258
256
} else {
259
257
configuration.logStores = [self .logStores copy ];
260
258
}
261
-
259
+
262
260
return configuration;
263
261
}
264
262
@@ -267,10 +265,10 @@ - (void)_createBugReportWithConfiguration:(ARKEmailBugReportConfiguration *)conf
267
265
NSMapTable *logStoresToLogMessagesMap = [NSMapTable new ];
268
266
NSMapTable *logStoresToLogMessagesAttachmentMap = [NSMapTable new ];
269
267
NSDictionary *emailBodyAdditions = [self .emailBodyAdditionsDelegate emailBodyAdditionsForEmailBugReporter: self ];
270
-
268
+
271
269
dispatch_group_t logStoreRetrievalDispatchGroup = dispatch_group_create ();
272
270
dispatch_group_enter (logStoreRetrievalDispatchGroup);
273
-
271
+
274
272
NSArray <ARKLogStore *> *const logStores = configuration.logStores ;
275
273
for (ARKLogStore *logStore in logStores) {
276
274
dispatch_group_enter (logStoreRetrievalDispatchGroup);
@@ -288,7 +286,7 @@ - (void)_createBugReportWithConfiguration:(ARKEmailBugReportConfiguration *)conf
288
286
dispatch_group_leave (logStoreRetrievalDispatchGroup);
289
287
}];
290
288
}
291
-
289
+
292
290
if ([MFMailComposeViewController canSendMail ]) {
293
291
self.mailComposeViewController = [MFMailComposeViewController new ];
294
292
@@ -358,19 +356,19 @@ - (void)_createBugReportWithConfiguration:(ARKEmailBugReportConfiguration *)conf
358
356
} else {
359
357
dispatch_group_notify (logStoreRetrievalDispatchGroup, dispatch_get_main_queue (), ^{
360
358
NSMutableString *const emailBody = [self _prefilledEmailBodyWithEmailBodyAdditions: emailBodyAdditions];
361
-
359
+
362
360
for (ARKLogStore *logStore in logStores) {
363
361
NSArray *const logMessages = [logStoresToLogMessagesMap objectForKey: logStore];
364
362
[emailBody appendFormat: @" %@ \n " , [self _recentErrorLogMessagesAsPlainText: logMessages count: self .numberOfRecentErrorLogsToIncludeInEmailBodyWhenAttachmentsAreUnavailable]];
365
363
}
366
-
364
+
367
365
NSURL *const composeEmailURL = [self _emailURLWithRecipients: @[self .bugReportRecipientEmailAddress] CC: @" " subject: configuration.prefilledEmailSubject body: emailBody];
368
366
if (composeEmailURL != nil ) {
369
367
[[UIApplication sharedApplication ] openURL: composeEmailURL options: @{} completionHandler: NULL ];
370
368
}
371
369
});
372
370
}
373
-
371
+
374
372
dispatch_group_leave (logStoreRetrievalDispatchGroup);
375
373
}
376
374
@@ -379,26 +377,26 @@ - (void)_showEmailComposeWindow;
379
377
self.previousKeyWindow = [ARKEmailBugReporter _keyWindow ];
380
378
381
379
[self .mailComposeViewController beginAppearanceTransition: YES animated: YES ];
382
-
380
+
383
381
self.emailComposeWindow .rootViewController = self.mailComposeViewController ;
384
382
[self .emailComposeWindow makeKeyAndVisible ];
385
-
383
+
386
384
[self .mailComposeViewController endAppearanceTransition ];
387
385
}
388
386
389
387
- (void )_dismissEmailComposeWindow ;
390
388
{
391
389
// Actually dismiss the mail compose view controller.
392
390
[self .mailComposeViewController beginAppearanceTransition: NO animated: YES ];
393
-
391
+
394
392
[self .mailComposeViewController.view removeFromSuperview ];
395
393
self.emailComposeWindow .rootViewController = nil ;
396
394
// Manually hide the window so that UIKit stops retaining it
397
395
self.emailComposeWindow .hidden = YES ;
398
396
self.emailComposeWindow = nil ;
399
-
397
+
400
398
[self .mailComposeViewController endAppearanceTransition ];
401
-
399
+
402
400
// Work around a bug introduced in iOS 9 where we don't get UIWindowDidBecomeKeyNotification when the mail compose view controller dismisses.
403
401
[self .previousKeyWindow makeKeyAndVisible ];
404
402
self.previousKeyWindow = nil ;
@@ -407,13 +405,13 @@ - (void)_dismissEmailComposeWindow;
407
405
- (NSMutableString *)_prefilledEmailBodyWithEmailBodyAdditions : (nullable NSDictionary *)emailBodyAdditions ;
408
406
{
409
407
NSMutableString *prefilledEmailBodyWithEmailBodyAdditions = [NSMutableString stringWithFormat: @" %@ \n " , self .prefilledEmailBody];
410
-
408
+
411
409
if (emailBodyAdditions.count > 0 ) {
412
410
for (NSString *emailBodyAdditionKey in emailBodyAdditions.allKeys ) {
413
411
[prefilledEmailBodyWithEmailBodyAdditions appendFormat: @" %@ : %@ \n " , emailBodyAdditionKey, emailBodyAdditions[emailBodyAdditionKey]];
414
412
}
415
413
}
416
-
414
+
417
415
// Add a newline to separate prefill email body and additions from what comes after.
418
416
[prefilledEmailBodyWithEmailBodyAdditions appendString: @" \n " ];
419
417
@@ -427,13 +425,13 @@ - (NSString *)_recentErrorLogMessagesAsPlainText:(NSArray *)logMessages count:(N
427
425
for (ARKLogMessage *log in [logMessages reverseObjectEnumerator ]) {
428
426
if (log .type == ARKLogTypeError) {
429
427
[recentErrorLogs appendFormat: @" %@ \n " , log ];
430
-
428
+
431
429
if (++failuresFound >= errorLogsToInclude) {
432
430
break ;
433
431
}
434
432
}
435
433
}
436
-
434
+
437
435
if (recentErrorLogs.length > 0 ) {
438
436
// Remove the final newline and create an immutable string.
439
437
return [recentErrorLogs stringByReplacingCharactersInRange: NSMakeRange (recentErrorLogs.length - 1 , 1 ) withString: @" " ];
@@ -446,33 +444,33 @@ - (NSURL *)_emailURLWithRecipients:(NSArray *)recipients CC:(NSString *)CCLine s
446
444
{
447
445
NSString *const defaultPrefix = @" mailto:" ;
448
446
NSArray *const prefixes = @[@" inbox-gmail://co" , @" sparrow://" , @" googlegmail:///co" , defaultPrefix];
449
-
447
+
450
448
NSURL *URL = nil ;
451
449
for (NSString *prefix in prefixes) {
452
450
URL = [self _emailURLWithPrefix: prefix recipients: recipients CC: CCLine subject: subjectLine body: bodyText shouldCheckCanOpenURL: YES ];
453
-
451
+
454
452
if (URL != nil ) {
455
453
break ;
456
454
}
457
455
}
458
-
456
+
459
457
ARKCheckCondition (URL != nil , [self _emailURLWithPrefix: defaultPrefix recipients: recipients CC: CCLine subject: subjectLine body: bodyText shouldCheckCanOpenURL: NO ], @" iOS prevented us from querying for URLs with %@ . Defaulting to %@ " , prefixes, defaultPrefix);
460
-
458
+
461
459
return URL;
462
460
}
463
461
464
462
- (NSURL *)_emailURLWithPrefix : (NSString *)prefix recipients : (NSArray *)recipients CC : (NSString *)CCLine subject : (NSString *)subjectLine body : (NSString *)bodyText shouldCheckCanOpenURL : (BOOL )shouldCheckCanOpenURL ;
465
463
{
466
464
NSString *const recipientsEscapedString = [[recipients componentsJoinedByString: @" ," ] stringByAddingPercentEncodingWithAllowedCharacters: NSCharacterSet .URLQueryAllowedCharacterSet];
467
-
465
+
468
466
NSString *const toArgument = (recipients.count > 0 ) ? [NSString stringWithFormat: @" to=%@ &" , recipientsEscapedString] : @" " ;
469
467
NSString *const URLString = [NSString stringWithFormat: @" %@ ?%@ cc=%@ &subject=%@ &body=%@ " ,
470
468
prefix,
471
469
toArgument,
472
470
[CCLine stringByAddingPercentEncodingWithAllowedCharacters: NSCharacterSet .URLQueryAllowedCharacterSet],
473
471
[subjectLine stringByAddingPercentEncodingWithAllowedCharacters: NSCharacterSet .URLQueryAllowedCharacterSet],
474
472
[bodyText stringByAddingPercentEncodingWithAllowedCharacters: NSCharacterSet .URLQueryAllowedCharacterSet]];
475
-
473
+
476
474
NSURL *const URL = [NSURL URLWithString: URLString];
477
475
if (shouldCheckCanOpenURL) {
478
476
return [[UIApplication sharedApplication ] canOpenURL: URL] ? URL : nil ;
@@ -531,33 +529,33 @@ - (void)showBugReportingPromptForConfiguration:(nonnull ARKEmailBugReportConfigu
531
529
Transfer first responder to an invisible view when a debug screenshot is captured to make bug filing itself bug-free.
532
530
*/
533
531
[self _stealFirstResponder ];
534
-
532
+
535
533
NSString * const title = NSLocalizedString(@" What Went Wrong?" , @" Title text for alert asking user to describe a bug they just encountered" );
536
534
NSString * const message = NSLocalizedString(@" Please briefly summarize the issue you just encountered. You’ll be asked for more details later." , @" Subtitle text for alert asking user to describe a bug they just encountered" );
537
535
NSString * const composeReportButtonTitle = NSLocalizedString(@" Compose Report" , @" Button title to compose bug report" );
538
536
NSString * const cancelButtonTitle = NSLocalizedString(@" Cancel" , @" Button title to not compose a bug report" );
539
-
537
+
540
538
UIAlertController *const alertController = [UIAlertController alertControllerWithTitle: title message: message preferredStyle: UIAlertControllerStyleAlert];
541
-
539
+
542
540
[alertController addAction: [UIAlertAction actionWithTitle: composeReportButtonTitle style: UIAlertActionStyleDefault handler: ^(UIAlertAction *action) {
543
541
UITextField *textfield = [alertController.textFields firstObject ];
544
542
configuration.prefilledEmailSubject = textfield.text ?: @" " ;
545
543
completion (configuration);
546
544
}]];
547
-
545
+
548
546
[alertController addAction: [UIAlertAction actionWithTitle: cancelButtonTitle style: UIAlertActionStyleDefault handler: ^(UIAlertAction *action) {
549
547
completion (nil );
550
548
}]];
551
-
549
+
552
550
[alertController addTextFieldWithConfigurationHandler: ^(UITextField *textField) {
553
551
[self _configureAlertTextfield: textField];
554
552
}];
555
-
553
+
556
554
UIViewController *viewControllerToPresentAlertController = [ARKEmailBugReporter _keyWindow ].rootViewController ;
557
555
while (viewControllerToPresentAlertController.presentedViewController != nil ) {
558
556
viewControllerToPresentAlertController = viewControllerToPresentAlertController.presentedViewController ;
559
557
}
560
-
558
+
561
559
/*
562
560
Disabling animations here to avoid potential crashes resulting from unexpected view state in UIKit
563
561
*/
0 commit comments