element-ios/Riot/Modules/BugReport/BugReportViewController.m

429 lines
14 KiB
Objective-C

/*
Copyright 2024 New Vector Ltd.
Copyright 2017 Vector Creations Ltd
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
#import "BugReportViewController.h"
#import "GeneratedInterface-Swift.h"
#import "GBDeviceInfo_iOS.h"
@interface BugReportViewController ()
{
MXBugReportRestClient *bugReportRestClient;
// The temporary file used to store the screenshot
NSURL *screenShotFile;
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
id kThemeServiceDidChangeThemeNotificationObserver;
}
@property (nonatomic) BOOL sendLogs;
@property (nonatomic) BOOL sendScreenshot;
@property (nonatomic) BOOL isSendingLogs;
@property (weak, nonatomic) IBOutlet UIView *overlayView;
@end
@implementation BugReportViewController
#pragma mark - Class methods
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([BugReportViewController class])
bundle:[NSBundle bundleForClass:[BugReportViewController class]]];
}
+ (instancetype)bugReportViewController
{
return [[[self class] alloc] initWithNibName:NSStringFromClass([BugReportViewController class])
bundle:[NSBundle bundleForClass:[BugReportViewController class]]];
}
#pragma mark -
- (void)showInViewController:(UIViewController *)viewController
{
self.providesPresentationContextTransitionStyle = YES;
self.definesPresentationContext = YES;
self.modalPresentationStyle = UIModalPresentationOverFullScreen;
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[viewController presentViewController:self animated:YES completion:nil];
}
- (void)viewDidLoad
{
[super viewDidLoad];
_logsDescriptionLabel.text = [VectorL10n bugReportLogsDescription];
_sendLogsLabel.text = [VectorL10n bugReportSendLogs];
_sendScreenshotLabel.text = [VectorL10n bugReportSendScreenshot];
_containerView.layer.cornerRadius = 20;
_bugReportDescriptionTextView.layer.borderWidth = 1.0f;
_bugReportDescriptionTextView.text = nil;
_bugReportDescriptionTextView.delegate = self;
if (_reportCrash)
{
_titleLabel.text = [VectorL10n bugCrashReportTitle];
_descriptionLabel.text = [VectorL10n bugCrashReportDescription];
}
else
{
_titleLabel.text = [VectorL10n bugReportTitle];
_descriptionLabel.text = [VectorL10n bugReportDescription];
}
[_cancelButton setTitle:[VectorL10n cancel] forState:UIControlStateNormal];
[_cancelButton setTitle:[VectorL10n cancel] forState:UIControlStateHighlighted];
[_sendButton setTitle:[VectorL10n bugReportSend] forState:UIControlStateNormal];
[_sendButton setTitle:[VectorL10n bugReportSend] forState:UIControlStateHighlighted];
[_backgroundButton setTitle:[VectorL10n bugReportBackgroundMode] forState:UIControlStateNormal];
[_backgroundButton setTitle:[VectorL10n bugReportBackgroundMode] forState:UIControlStateHighlighted];
// Do not send empty report
_sendButton.enabled = NO;;
_sendingContainer.hidden = YES;
self.sendLogs = YES;
self.sendScreenshot = YES;
// Hide the screenshot button if there is no screenshot
if (!_screenshot)
{
_sendScreenshotContainer.hidden = YES;
_sendScreenshotContainerHeightConstraint.constant = 0;
}
// Listen to sendLogs tap
UITapGestureRecognizer *sendLogsTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSendLogsTap:)];
[sendLogsTapGesture setNumberOfTouchesRequired:1];
[_sendLogsContainer addGestureRecognizer:sendLogsTapGesture];
_sendLogsContainer.userInteractionEnabled = YES;
// Listen to sendScreenshot tap
UITapGestureRecognizer *sendScreenshotTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSendScreenshotTap:)];
[sendScreenshotTapGesture setNumberOfTouchesRequired:1];
[_sendScreenshotContainer addGestureRecognizer:sendScreenshotTapGesture];
_sendScreenshotContainer.userInteractionEnabled = YES;
// Add an accessory view in order to retrieve keyboard view
_bugReportDescriptionTextView.inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
// Observe user interface theme change.
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self userInterfaceThemeDidChange];
}];
[self userInterfaceThemeDidChange];
UIGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(backgroundViewTapped)];
[self.view addGestureRecognizer:recognizer];
}
- (void)userInterfaceThemeDidChange
{
[ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar];
self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor;
self.overlayView.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor;
self.overlayView.alpha = 1.0;
self.containerView.backgroundColor = ThemeService.shared.theme.backgroundColor;
self.sendingContainer.backgroundColor = ThemeService.shared.theme.backgroundColor;
self.bugReportDescriptionTextView.keyboardAppearance = ThemeService.shared.theme.keyboardAppearance;
self.titleLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
self.sendingLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
self.descriptionLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
self.bugReportDescriptionTextView.textColor = ThemeService.shared.theme.textPrimaryColor;
self.bugReportDescriptionTextView.tintColor = ThemeService.shared.theme.tintColor;
self.logsDescriptionLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
self.sendLogsLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
self.sendScreenshotLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
self.sendButton.tintColor = ThemeService.shared.theme.tintColor;
self.cancelButton.tintColor = ThemeService.shared.theme.tintColor;
self.backgroundButton.tintColor = ThemeService.shared.theme.tintColor;
_bugReportDescriptionTextView.layer.borderColor = ThemeService.shared.theme.headerBackgroundColor.CGColor;
self.sendLogsButtonImage.tintColor = ThemeService.shared.theme.tintColor;
self.sendScreenshotButtonImage.tintColor = ThemeService.shared.theme.tintColor;
[self setNeedsStatusBarAppearanceUpdate];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return ThemeService.shared.theme.statusBarStyle;
}
- (void)destroy
{
if (kThemeServiceDidChangeThemeNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver];
kThemeServiceDidChangeThemeNotificationObserver = nil;
}
[super destroy];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
[super dismissViewControllerAnimated:flag completion:completion];
[self destroy];
}
- (void)dealloc
{
_bugReportDescriptionTextView.inputAccessoryView = nil;
}
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self dismissKeyboard];
if (screenShotFile)
{
[[NSFileManager defaultManager] removeItemAtURL:screenShotFile error:nil];
screenShotFile = nil;
}
}
- (void)setSendLogs:(BOOL)sendLogs
{
_sendLogs = sendLogs;
if (_sendLogs)
{
_sendLogsButtonImage.image = AssetImages.selectionTick.image;
}
else
{
_sendLogsButtonImage.image = AssetImages.selectionUntick.image;
}
}
- (void)setSendScreenshot:(BOOL)sendScreenshot
{
_sendScreenshot = sendScreenshot;
if (_sendScreenshot)
{
_sendScreenshotButtonImage.image = AssetImages.selectionTick.image;
}
else
{
_sendScreenshotButtonImage.image = AssetImages.selectionUntick.image;
}
}
- (void)setIsSendingLogs:(BOOL)isSendingLogs
{
_isSendingLogs = isSendingLogs;
_sendButton.hidden = isSendingLogs;
_sendingContainer.hidden = !isSendingLogs;
_backgroundButton.hidden = !isSendingLogs;
}
#pragma mark - MXKViewController
- (void)dismissKeyboard
{
// Hide the keyboard
[_bugReportDescriptionTextView resignFirstResponder];
}
- (void)onKeyboardShowAnimationComplete
{
self.keyboardView = _bugReportDescriptionTextView.inputAccessoryView.superview;
}
-(void)setKeyboardHeight:(CGFloat)keyboardHeight
{
// In portrait in 6/7 and 6+/7+, make the height of the popup smaller to be able to
// display Cancel and Send buttons.
// Do nothing in landscape or in 5 in portrait and in landscape. There will be not enough
// room to display bugReportDescriptionTextView.
if (self.view.frame.size.height > 568)
{
self.scrollViewBottomConstraint.constant = keyboardHeight;
}
else
{
self.scrollViewBottomConstraint.constant = 0;
}
[self.view layoutIfNeeded];
}
#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView
{
_sendButton.enabled = (_bugReportDescriptionTextView.text.length != 0);
}
#pragma mark - User actions
- (IBAction)onSendButtonPress:(id)sender
{
self.isSendingLogs = YES;
bugReportRestClient = [MXBugReportRestClient vc_bugReportRestClientWithAppName:BuildSettings.bugReportApplicationId];
// Screenshot
NSArray<NSURL*> *files;
if (_screenshot && _sendScreenshot)
{
// Store the screenshot into a temporary file
NSData *screenShotData = UIImagePNGRepresentation(_screenshot);
screenShotFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"screenshot.png"]];
[screenShotData writeToURL:screenShotFile atomically:YES];
files = @[screenShotFile];
}
NSMutableString *bugReportDescription = [NSMutableString stringWithString:_bugReportDescriptionTextView.text];
// starting a background task to have a bit of extra time in case of user forgets about the report and sends the app to background
__block UIBackgroundTaskIdentifier operationBackgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
operationBackgroundId = UIBackgroundTaskInvalid;
}];
[bugReportRestClient vc_sendBugReportWithDescription:bugReportDescription
sendLogs:_sendLogs
sendCrashLog:_reportCrash
sendFiles:files
additionalLabels:nil
customFields:nil
progress:^(MXBugReportState state, NSProgress *progress) {
switch (state)
{
case MXBugReportStateProgressZipping:
self.sendingLabel.text = [VectorL10n bugReportProgressZipping];
break;
case MXBugReportStateProgressUploading:
self.sendingLabel.text = [VectorL10n bugReportProgressUploading];
break;
default:
break;
}
self.sendingProgress.progress = progress.fractionCompleted;
} success:^(NSString *reportUrl){
self->bugReportRestClient = nil;
if (self.reportCrash)
{
// Erase the crash log
[MXLogger deleteCrashLog];
}
[self dismissViewControllerAnimated:YES completion:nil];
if (operationBackgroundId != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
operationBackgroundId = UIBackgroundTaskInvalid;
}
} failure:^(NSError *error) {
if (self.presentingViewController)
{
self->bugReportRestClient = nil;
[[AppDelegate theDelegate] showErrorAsAlert:error];
self.isSendingLogs = NO;
}
else
{
[[[UIApplication sharedApplication].windows firstObject].rootViewController presentViewController:self animated:YES completion:^{
self->bugReportRestClient = nil;
[[AppDelegate theDelegate] showErrorAsAlert:error];
self.isSendingLogs = NO;
}];
}
if (operationBackgroundId != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
operationBackgroundId = UIBackgroundTaskInvalid;
}
}];
}
- (IBAction)onCancelButtonPressed:(id)sender
{
if (bugReportRestClient)
{
// If the submission is in progress, cancel the sending and come back
// to the bug report screen
[bugReportRestClient cancel];
bugReportRestClient = nil;
self.isSendingLogs = NO;
}
else
{
if (_reportCrash)
{
// Erase the crash log
[MXLogger deleteCrashLog];
}
// Else, lease the bug report screen
[self dismissViewControllerAnimated:YES completion:nil];
}
}
- (IBAction)onSendLogsTap:(id)sender
{
self.sendLogs = !self.sendLogs;
}
- (IBAction)onSendScreenshotTap:(id)sender
{
self.sendScreenshot = !self.sendScreenshot;
}
- (IBAction)onBackgroundTap:(id)sender
{
[self dismissViewControllerAnimated:YES completion:^{}];
}
- (void)backgroundViewTapped
{
// Dismiss keyboard if user taps on background view: https://github.com/vector-im/element-ios/issues/3819
[self.bugReportDescriptionTextView resignFirstResponder];
}
@end