ente/lib/utils/email_util.dart

268 lines
8.1 KiB
Dart
Raw Normal View History

2021-06-12 10:33:24 +00:00
import 'dart:io';
import 'package:archive/archive_io.dart';
2021-05-29 22:48:23 +00:00
import 'package:email_validator/email_validator.dart';
import 'package:flutter/cupertino.dart';
2021-08-09 14:12:42 +00:00
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
2021-06-12 10:33:24 +00:00
import 'package:flutter_email_sender/flutter_email_sender.dart';
2022-03-05 21:22:45 +00:00
import 'package:logging/logging.dart';
2022-07-11 09:35:38 +00:00
import 'package:open_mail_app/open_mail_app.dart';
import 'package:package_info_plus/package_info_plus.dart';
2021-06-12 10:33:24 +00:00
import 'package:path_provider/path_provider.dart';
2022-06-23 04:12:58 +00:00
import 'package:photos/core/configuration.dart';
import 'package:photos/core/error-reporting/super_logging.dart';
import 'package:photos/ui/components/button_widget.dart';
import 'package:photos/ui/components/dialog_widget.dart';
import 'package:photos/ui/components/models/button_type.dart';
2022-07-01 14:39:02 +00:00
import 'package:photos/ui/tools/debug/log_file_viewer.dart';
2021-06-12 10:33:24 +00:00
import 'package:photos/utils/dialog_util.dart';
2022-07-11 09:35:38 +00:00
import 'package:photos/utils/toast_util.dart';
2021-10-05 04:56:48 +00:00
import 'package:share_plus/share_plus.dart';
2022-07-11 09:35:38 +00:00
import 'package:url_launcher/url_launcher.dart';
2021-05-29 22:48:23 +00:00
2022-03-05 21:22:45 +00:00
final Logger _logger = Logger('email_util');
2022-12-30 10:37:20 +00:00
bool isValidEmail(String? email) {
if (email == null) {
return false;
}
2021-05-29 22:48:23 +00:00
return EmailValidator.validate(email);
2020-10-09 21:45:07 +00:00
}
2021-06-12 10:33:24 +00:00
Future<void> sendLogs(
BuildContext context,
2021-08-09 14:12:42 +00:00
String title,
String toEmail, {
2022-12-30 10:37:20 +00:00
Function? postShare,
String? subject,
String? body,
2021-08-09 14:12:42 +00:00
}) async {
showDialogWidget(
context: context,
title: "Report a bug",
icon: Icons.bug_report_outlined,
body:
2022-06-23 07:25:45 +00:00
"This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.",
buttons: [
ButtonWidget(
isInAlert: true,
buttonType: ButtonType.neutral,
labelText: "Report a bug",
buttonAction: ButtonAction.first,
shouldSurfaceExecutionStates: false,
onTap: () async {
await _sendLogs(context, toEmail, subject, body);
if (postShare != null) {
postShare();
}
},
2021-08-09 14:35:17 +00:00
),
//isInAlert is false here as we don't want to the dialog to dismiss
//on pressing this button
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: "View logs",
buttonAction: ButtonAction.second,
onTap: () async {
showDialog(
context: context,
builder: (BuildContext context) {
return LogFileViewer(SuperLogging.logFile!);
},
barrierColor: Colors.black87,
barrierDismissible: false,
);
},
2021-08-09 14:12:42 +00:00
),
const ButtonWidget(
isInAlert: true,
buttonType: ButtonType.secondary,
labelText: "Cancel",
buttonAction: ButtonAction.cancel,
2021-08-09 14:35:17 +00:00
),
],
2021-08-09 14:12:42 +00:00
);
}
Future<void> _sendLogs(
2022-06-11 08:23:52 +00:00
BuildContext context,
String toEmail,
2022-12-30 10:37:20 +00:00
String? subject,
String? body,
2022-06-11 08:23:52 +00:00
) async {
2022-08-29 14:43:31 +00:00
final String zipFilePath = await getZippedLogsFile(context);
2021-06-12 10:33:24 +00:00
final Email email = Email(
recipients: [toEmail],
2022-12-30 10:37:20 +00:00
subject: subject ?? '',
body: body ?? '',
2021-06-12 10:33:24 +00:00
attachmentPaths: [zipFilePath],
isHTML: false,
);
try {
await FlutterEmailSender.send(email);
2022-03-05 21:22:45 +00:00
} catch (e, s) {
_logger.severe('email sender failed', e, s);
Navigator.of(context, rootNavigator: true).pop();
await shareLogs(context, toEmail, zipFilePath);
}
}
Future<String> getZippedLogsFile(BuildContext context) async {
2022-06-06 13:42:27 +00:00
final dialog = createProgressDialog(context, "Preparing logs...");
await dialog.show();
2022-06-23 04:12:58 +00:00
final logsPath = (await getApplicationSupportDirectory()).path;
final logsDirectory = Directory(logsPath + "/logs");
final tempPath = (await getTemporaryDirectory()).path;
final zipFilePath =
tempPath + "/logs-${Configuration.instance.getUserID() ?? 0}.zip";
2022-08-29 14:43:31 +00:00
final encoder = ZipFileEncoder();
encoder.create(zipFilePath);
encoder.addDirectory(logsDirectory);
encoder.close();
await dialog.hide();
return zipFilePath;
}
Future<void> shareLogs(
2022-06-11 08:23:52 +00:00
BuildContext context,
String toEmail,
String zipFilePath,
) async {
final result = await showDialogWidget(
context: context,
title: "Email your logs",
body: "Please send the logs to \n$toEmail",
buttons: [
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Copy email address",
isInAlert: true,
buttonAction: ButtonAction.first,
onTap: () async {
await Clipboard.setData(ClipboardData(text: toEmail));
},
2023-01-10 06:22:25 +00:00
shouldShowSuccessConfirmation: true,
),
const ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Export logs",
isInAlert: true,
buttonAction: ButtonAction.second,
),
const ButtonWidget(
buttonType: ButtonType.secondary,
labelText: "Cancel",
isInAlert: true,
buttonAction: ButtonAction.cancel,
),
],
2022-06-11 08:23:52 +00:00
);
if (result?.action != null && result!.action == ButtonAction.second) {
final Size size = MediaQuery.of(context).size;
await Share.shareFiles(
[zipFilePath],
sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2),
);
2021-06-12 10:33:24 +00:00
}
}
2022-07-11 09:35:38 +00:00
Future<void> sendEmail(
BuildContext context, {
2022-12-30 10:37:20 +00:00
required String to,
String? subject,
String? body,
2022-07-11 09:35:38 +00:00
}) async {
try {
2022-08-29 14:43:31 +00:00
final String clientDebugInfo = await _clientInfo();
2022-12-30 15:42:03 +00:00
final EmailContent emailContent = EmailContent(
2022-07-11 09:35:38 +00:00
to: [
to,
],
subject: subject ?? '[Support]',
body: (body ?? '') + clientDebugInfo,
);
if (Platform.isAndroid) {
// Special handling due to issue in proton mail android client
// https://github.com/ente-io/photos-app/pull/253
2022-07-11 09:35:38 +00:00
final Uri params = Uri(
scheme: 'mailto',
path: to,
2022-12-30 15:42:03 +00:00
query: 'subject=${emailContent.subject}&body=${emailContent.body}',
2022-07-11 09:35:38 +00:00
);
if (await canLaunchUrl(params)) {
await launchUrl(params);
} else {
// this will trigger _showNoMailAppsDialog
throw Exception('Could not launch ${params.toString()}');
}
} else {
final OpenMailAppResult result =
await OpenMailApp.composeNewEmailInMailApp(
2022-12-30 15:42:03 +00:00
nativePickerTitle: 'Select emailContent app',
emailContent: emailContent,
2022-07-11 09:35:38 +00:00
);
if (!result.didOpen && !result.canOpen) {
_showNoMailAppsDialog(context, to);
} else if (!result.didOpen && result.canOpen) {
2022-07-12 04:06:55 +00:00
await showCupertinoModalPopup(
2022-07-11 09:35:38 +00:00
context: context,
builder: (_) => CupertinoActionSheet(
title: Text("Select mail app \n $to"),
actions: [
for (var app in result.options)
CupertinoActionSheetAction(
child: Text(app.name),
onPressed: () {
2022-12-30 15:42:03 +00:00
final content = emailContent;
OpenMailApp.composeNewEmailInSpecificMailApp(
mailApp: app,
emailContent: content,
);
2022-07-12 04:06:55 +00:00
Navigator.of(context, rootNavigator: true).pop();
},
),
],
cancelButton: CupertinoActionSheetAction(
child: const Text("Cancel"),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
),
2022-07-11 09:35:38 +00:00
),
);
}
}
} catch (e) {
2022-12-30 15:42:03 +00:00
_logger.severe("Failed to send emailContent to $to", e);
2022-07-11 09:35:38 +00:00
_showNoMailAppsDialog(context, to);
}
}
2022-07-11 10:43:26 +00:00
Future<String> _clientInfo() async {
2022-07-11 09:35:38 +00:00
final packageInfo = await PackageInfo.fromPlatform();
final String debugInfo =
'\n\n\n\n ------------------- \nFollowing information can '
2022-07-11 09:35:38 +00:00
'help us in debugging if you are facing any issue '
'\nRegistered email: ${Configuration.instance.getEmail()}'
'\nClient: ${packageInfo.packageName}'
'\nVersion : ${packageInfo.version}';
return debugInfo;
}
void _showNoMailAppsDialog(BuildContext context, String toEmail) {
showChoiceDialog(
context,
icon: Icons.email_outlined,
title: 'Please email us at $toEmail',
firstButtonLabel: "Copy email address",
secondButtonLabel: "Dismiss",
firstButtonOnTap: () async {
await Clipboard.setData(ClipboardData(text: toEmail));
showShortToast(context, 'Copied');
2022-07-11 09:35:38 +00:00
},
);
}