Skip to content

Commit

Permalink
Migrate screenshots to XFiles
Browse files Browse the repository at this point in the history
  • Loading branch information
ThexXTURBOXx committed May 19, 2024
1 parent f1b99b9 commit 139e455
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 53 deletions.
18 changes: 9 additions & 9 deletions lib/core/catcher_2.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'dart:isolate';

import 'package:catcher_2/core/application_profile_manager.dart';
Expand All @@ -14,6 +13,7 @@ import 'package:catcher_2/model/report_handler.dart';
import 'package:catcher_2/model/report_mode.dart';
import 'package:catcher_2/utils/catcher_2_error_widget.dart';
import 'package:catcher_2/utils/catcher_2_logger.dart';
import 'package:cross_file/cross_file.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -460,7 +460,9 @@ class Catcher2 implements ReportModeAction {
screenshotManager = Catcher2ScreenshotManager(_logger);
final screenshotsPath = _currentConfig.screenshotsPath;
if (!ApplicationProfileManager.isWeb() && screenshotsPath.isEmpty) {
_logger.warning("Screenshots path is empty. Screenshots won't work.");
_logger.warning(
"Screenshots path is empty. Screenshots won't be saved locally.",
);
}
screenshotManager.path = screenshotsPath;
}
Expand Down Expand Up @@ -494,13 +496,11 @@ class Catcher2 implements ReportModeAction {

_cleanPastReportsOccurrences();

File? screenshot;
if (!ApplicationProfileManager.isWeb()) {
try {
screenshot = await screenshotManager.captureAndSave();
} catch (e) {
_logger.warning('Failed to create screenshot file: $e');
}
XFile? screenshot;
try {
screenshot = await screenshotManager.captureAndSave();
} catch (e) {
_logger.warning('Failed to create screenshot file: $e');
}

final report = Report(
Expand Down
17 changes: 8 additions & 9 deletions lib/core/catcher_2_screenshot_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
library screenshot;

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:catcher_2/utils/catcher_2_logger.dart';
import 'package:cross_file/cross_file.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
Expand All @@ -24,14 +24,11 @@ class Catcher2ScreenshotManager {

/// Create screenshot and save it in file. File will be created in directory
/// specified in `Catcher2Options`.
Future<File?> captureAndSave({
Future<XFile?> captureAndSave({
double? pixelRatio,
Duration delay = const Duration(milliseconds: 20),
}) async {
try {
if (_path?.isEmpty ?? true) {
return null;
}
final content = await _capture(
pixelRatio: pixelRatio,
delay: delay,
Expand All @@ -46,11 +43,13 @@ class Catcher2ScreenshotManager {
return null;
}

Future<File> saveFile(Uint8List fileContent) async {
assert(_path != null && _path!.isNotEmpty, 'path is empty');
Future<XFile> saveFile(Uint8List fileContent) async {
final name = 'catcher_2_${DateTime.now().microsecondsSinceEpoch}.png';
final file = await File('$_path/$name').create(recursive: true);
file.writeAsBytesSync(fileContent);
final path = (_path?.isEmpty ?? true) ? name : '$_path/$name';
final file = XFile.fromData(fileContent, path: path, name: name);
if (_path != null && _path!.isNotEmpty) {
await file.saveTo(path);
}
return file;
}

Expand Down
36 changes: 21 additions & 15 deletions lib/handlers/discord_handler.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'dart:async';
import 'dart:io';

import 'package:catcher_2/model/platform_type.dart';
import 'package:catcher_2/model/report.dart';
import 'package:catcher_2/model/report_handler.dart';
import 'package:catcher_2/utils/catcher_2_utils.dart';
import 'package:cross_file/cross_file.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

Expand Down Expand Up @@ -106,30 +106,36 @@ class DiscordHandler extends ReportHandler {
return stringBuffer.toString();
}

Future<bool> _sendContent(String content, File? screenshot) async {
Future<bool> _sendContent(String content, XFile? screenshot) async {
try {
_printLog('Sending request to Discord server...');
Response<dynamic>? response;

final data = <String, dynamic>{
'content': content,
};

if (screenshot != null) {
final screenshotPath = screenshot.path;
final formData = FormData.fromMap(<String, dynamic>{
'content': content,
'file': await MultipartFile.fromFile(screenshotPath),
});
response = await _dio.post<dynamic>(webhookUrl, data: formData);
} else {
final data = {
'content': content,
};
response = await _dio.post<dynamic>(webhookUrl, data: data);
data.addAll(
{
'file': MultipartFile.fromBytes(
await screenshot.readAsBytes(),
filename: screenshot.name,
),
},
);
}

response = await _dio.post<dynamic>(
webhookUrl,
data: FormData.fromMap(data),
);

_printLog(
'Server responded with code: ${response.statusCode} and message: '
'${response.statusMessage}',
);
final statusCode = response.statusCode ?? 0;
return statusCode >= 200 && statusCode < 300;
return response.ok;
} catch (exception) {
_printLog('Failed to send data to Discord server: $exception');
return false;
Expand Down
17 changes: 16 additions & 1 deletion lib/handlers/email_auto_handler.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import 'dart:async';

import 'package:catcher_2/catcher_2.dart';
import 'package:catcher_2/handlers/base_email_handler.dart';
import 'package:catcher_2/model/platform_type.dart';
import 'package:catcher_2/model/report.dart';
import 'package:cross_file/cross_file.dart';
import 'package:flutter/material.dart';
import 'package:mailer/mailer.dart';
import 'package:mailer/smtp_server.dart';
Expand Down Expand Up @@ -48,7 +52,7 @@ class EmailAutoHandler extends BaseEmailHandler {
..text = setupRawMessageText(report);

if (report.screenshot != null) {
message.attachments = [FileAttachment(report.screenshot!)];
message.attachments = [XFilePngAttachment(report.screenshot!)];
}

if (sendHtml) {
Expand Down Expand Up @@ -96,3 +100,14 @@ class EmailAutoHandler extends BaseEmailHandler {
PlatformType.windows,
];
}

class XFilePngAttachment extends Attachment {
XFilePngAttachment(this._xFile) {
contentType = 'image/png';
}

final XFile _xFile;

@override
Stream<List<int>> asStream() => _xFile.openRead();
}
27 changes: 18 additions & 9 deletions lib/handlers/slack_handler.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';

import 'package:catcher_2/model/platform_type.dart';
import 'package:catcher_2/model/report.dart';
Expand Down Expand Up @@ -66,18 +67,21 @@ class SlackHandler extends ReportHandler {

if (screenshot != null) {
data.addAll(
await _tryUploadScreenshot(screenshot: XFile(screenshot.path)),
await _tryUploadScreenshot(screenshot: screenshot),
);
}

final response = await _dio.post<dynamic>(webhookUrl, data: data);
final response = await _dio.post<dynamic>(
webhookUrl,
data: json.encode(data),
options: Options(contentType: Headers.formUrlEncodedContentType),
);
_printLog(
'Server responded with code: ${response.statusCode} and '
'message: ${response.statusMessage}',
);

final statusCode = response.statusCode ?? 0;
return statusCode >= 200 && statusCode < 300;
return response.ok;
} catch (exception) {
_printLog('Failed to send slack message: $exception');
return false;
Expand All @@ -96,8 +100,7 @@ class SlackHandler extends ReportHandler {
}

try {
final screenshotPath = screenshot.path;
final name = 'catcher_2_${DateTime.now().microsecondsSinceEpoch}.png';
final name = screenshot.name;

final formData = FormData.fromMap(<String, dynamic>{
'token': apiToken,
Expand Down Expand Up @@ -125,16 +128,21 @@ class SlackHandler extends ReportHandler {
}

final formDataPost = FormData.fromMap(<String, dynamic>{
'file': await MultipartFile.fromFile(screenshotPath),
'token': apiToken,
'file': MultipartFile.fromBytes(
await screenshot.readAsBytes(),
filename: screenshot.name,
),
});
final responseFilePost = await _dio.post<dynamic>(
responseFile.data['upload_url'],
data: formDataPost,
options: Options(
contentType: Headers.multipartFormDataContentType,
validateStatus: (e) => true,
),
);
if (responseFilePost.statusCode != 200) {
if (!responseFilePost.ok) {
_printLog(
'Server responded to upload file post with code: '
'${responseFilePost.statusCode} '
Expand Down Expand Up @@ -171,7 +179,8 @@ class SlackHandler extends ReportHandler {
'attachments': [
{
'image_url': responseFileComplete.data['files'][0]['url_private'],
'text': responseFileComplete.data['files'][0]['permalink'],
'text': 'Screenshot will soon be available here: '
'${responseFileComplete.data['files'][0]['permalink']}',
},
],
};
Expand Down
5 changes: 2 additions & 3 deletions lib/model/report.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:io';

import 'package:catcher_2/model/platform_type.dart';
import 'package:cross_file/cross_file.dart';
import 'package:flutter/foundation.dart';

class Report {
Expand Down Expand Up @@ -43,7 +42,7 @@ class Report {

/// Screenshot of screen where error happens. Screenshot won't work everywhere
/// (i.e. web platform), so this may be null.
final File? screenshot;
final XFile? screenshot;

/// Creates json from current instance
Map<String, dynamic> toJson({
Expand Down
23 changes: 16 additions & 7 deletions lib/utils/catcher_2_utils.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import 'dart:io';

import 'package:catcher_2/core/application_profile_manager.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:universal_io/io.dart';

class Catcher2Utils {
/// From https://stackoverflow.com/a/56959146/5894824
static Future<bool> isInternetConnectionAvailable() async {
try {
final result = await InternetAddress.lookup('google.com');
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
} catch (exception) {
return false;
if (ApplicationProfileManager.isWeb()) {
return true; // TODO(HyperSpeeed): We could in theory handle this maybe?
} else {
try {
final result = await InternetAddress.lookup('google.com');
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
} catch (_) {}
}
return false;
}

static bool isCupertinoAppAncestor(BuildContext context) =>
context.findAncestorWidgetOfExactType<CupertinoApp>() != null;
}

/// From https://stackoverflow.com/a/70282800/5894824
extension IsOk on Response {
bool get ok => statusCode != null && (statusCode! ~/ 100) == 2;
}

0 comments on commit 139e455

Please sign in to comment.