123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- import 'dart:io';
- import 'package:archive/archive_io.dart';
- import 'package:email_validator/email_validator.dart';
- import 'package:ente_auth/core/configuration.dart';
- import 'package:ente_auth/core/logging/super_logging.dart';
- import 'package:ente_auth/l10n/l10n.dart';
- import 'package:ente_auth/ui/components/buttons/button_widget.dart';
- import 'package:ente_auth/ui/components/dialog_widget.dart';
- import 'package:ente_auth/ui/components/models/button_type.dart';
- import 'package:ente_auth/ui/tools/debug/log_file_viewer.dart';
- import 'package:ente_auth/utils/dialog_util.dart';
- import 'package:ente_auth/utils/platform_util.dart';
- import 'package:ente_auth/utils/share_utils.dart';
- import 'package:ente_auth/utils/toast_util.dart';
- import "package:file_saver/file_saver.dart";
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_email_sender/flutter_email_sender.dart';
- import "package:intl/intl.dart";
- import 'package:logging/logging.dart';
- import 'package:package_info_plus/package_info_plus.dart';
- import 'package:path_provider/path_provider.dart';
- import 'package:share_plus/share_plus.dart';
- import 'package:url_launcher/url_launcher.dart';
- final Logger _logger = Logger('email_util');
- bool isValidEmail(String? email) {
- if (email == null) {
- return false;
- }
- return EmailValidator.validate(email);
- }
- Future<void> sendLogs(
- BuildContext context,
- String title,
- String toEmail, {
- Function? postShare,
- String? subject,
- String? body,
- }) async {
- final l10n = context.l10n;
- await showDialogWidget(
- context: context,
- title: title,
- icon: Icons.bug_report_outlined,
- body: l10n.sendLogsDescription,
- buttons: [
- ButtonWidget(
- isInAlert: true,
- buttonType: ButtonType.neutral,
- labelText: l10n.reportABug,
- buttonAction: ButtonAction.first,
- shouldSurfaceExecutionStates: false,
- onTap: () async {
- await _sendLogs(context, toEmail, subject, body);
- if (postShare != null) {
- postShare();
- }
- },
- ),
- //isInAlert is false here as we don't want to the dialog to dismiss
- //on pressing this button
- ButtonWidget(
- buttonType: ButtonType.secondary,
- labelText: l10n.viewLogsAction,
- buttonAction: ButtonAction.second,
- onTap: () async {
- await showDialog(
- context: context,
- builder: (BuildContext context) {
- return LogFileViewer(SuperLogging.logFile!);
- },
- barrierColor: Colors.black87,
- barrierDismissible: false,
- );
- },
- ),
- ButtonWidget(
- isInAlert: true,
- buttonType: ButtonType.secondary,
- labelText: l10n.exportLogs,
- buttonAction: ButtonAction.third,
- onTap: () async {
- Future.delayed(
- const Duration(milliseconds: 200),
- () => shareDialog(
- context,
- title,
- saveAction: () async {
- final zipFilePath = await getZippedLogsFile(context);
- await exportLogs(context, zipFilePath);
- },
- sendAction: () async {
- final zipFilePath = await getZippedLogsFile(context);
- await exportLogs(context, zipFilePath, true);
- },
- ),
- );
- },
- ),
- ButtonWidget(
- isInAlert: true,
- buttonType: ButtonType.secondary,
- labelText: l10n.cancel,
- buttonAction: ButtonAction.cancel,
- ),
- ],
- );
- }
- Future<void> _sendLogs(
- BuildContext context,
- String toEmail,
- String? subject,
- String? body,
- ) async {
- final String zipFilePath = await getZippedLogsFile(context);
- final Email email = Email(
- recipients: [toEmail],
- subject: subject ?? '',
- body: body ?? '',
- attachmentPaths: [zipFilePath],
- isHTML: false,
- );
- try {
- await FlutterEmailSender.send(email);
- } 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 {
- final l10n = context.l10n;
- final dialog = createProgressDialog(context, l10n.preparingLogsTitle);
- await dialog.show();
- final logsPath = (await getApplicationSupportDirectory()).path;
- final logsDirectory = Directory("$logsPath/logs");
- final tempPath = (await getTemporaryDirectory()).path;
- final zipFilePath =
- "$tempPath/logs-${Configuration.instance.getUserID() ?? 0}.zip";
- final encoder = ZipFileEncoder();
- encoder.create(zipFilePath);
- await encoder.addDirectory(logsDirectory);
- encoder.close();
- await dialog.hide();
- return zipFilePath;
- }
- Future<void> shareLogs(
- BuildContext context,
- String toEmail,
- String zipFilePath,
- ) async {
- final l10n = context.l10n;
- final result = await showDialogWidget(
- context: context,
- title: l10n.emailYourLogs,
- body: l10n.pleaseSendTheLogsTo(toEmail),
- buttons: [
- ButtonWidget(
- buttonType: ButtonType.neutral,
- labelText: l10n.copyEmailAddress,
- isInAlert: true,
- buttonAction: ButtonAction.first,
- onTap: () async {
- await Clipboard.setData(ClipboardData(text: toEmail));
- },
- shouldShowSuccessConfirmation: true,
- ),
- ButtonWidget(
- buttonType: ButtonType.neutral,
- labelText: l10n.exportLogs,
- isInAlert: true,
- buttonAction: ButtonAction.second,
- ),
- ButtonWidget(
- buttonType: ButtonType.secondary,
- labelText: l10n.cancel,
- isInAlert: true,
- buttonAction: ButtonAction.cancel,
- ),
- ],
- );
- if (result?.action != null && result!.action == ButtonAction.second) {
- Future.delayed(
- const Duration(milliseconds: 200),
- () => shareDialog(
- context,
- context.l10n.exportLogs,
- saveAction: () async {
- final zipFilePath = await getZippedLogsFile(context);
- await exportLogs(context, zipFilePath);
- },
- sendAction: () async {
- final zipFilePath = await getZippedLogsFile(context);
- await exportLogs(context, zipFilePath, true);
- },
- ),
- );
- }
- }
- Future<void> exportLogs(
- BuildContext context,
- String zipFilePath, [
- bool isSharing = false,
- ]) async {
- final Size size = MediaQuery.of(context).size;
- if (!isSharing) {
- final DateTime now = DateTime.now().toUtc();
- final String shortMonthName = DateFormat('MMM').format(now); // Short month
- final String logFileName =
- 'ente-logs-${now.year}-$shortMonthName-${now.day}-${now.hour}-${now.minute}';
- final bytes = await File(zipFilePath).readAsBytes();
- await PlatformUtil.shareFile(
- logFileName,
- 'zip',
- bytes,
- MimeType.zip,
- );
- } else {
- await Share.shareXFiles(
- [XFile(zipFilePath, mimeType: 'application/zip')],
- sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2),
- );
- }
- }
- Future<void> sendEmail(
- BuildContext context, {
- required String to,
- String? subject,
- String? body,
- }) async {
- try {
- final String clientDebugInfo = await _clientInfo();
- final String subject0 = subject ?? '[Support]';
- final String body0 = (body ?? '') + clientDebugInfo;
- // final EmailContent email = EmailContent(
- // 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/frame/pull/253
- final Uri params = Uri(
- scheme: 'mailto',
- path: to,
- query: 'subject=$subject0&body=$body0',
- );
- if (await canLaunchUrl(params)) {
- await launchUrl(params);
- } else {
- // this will trigger _showNoMailAppsDialog
- throw Exception('Could not launch ${params.toString()}');
- }
- } else {
- _showNoMailAppsDialog(context, to);
- }
- } catch (e) {
- _logger.severe("Failed to send email to $to", e);
- _showNoMailAppsDialog(context, to);
- }
- }
- Future<String> _clientInfo() async {
- final packageInfo = await PackageInfo.fromPlatform();
- final String debugInfo =
- '\n\n\n\n ------------------- \nFollowing information can '
- '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) {
- final l10n = context.l10n;
- showChoiceDialog(
- context,
- icon: Icons.email_outlined,
- title: l10n.emailUsMessage(toEmail),
- firstButtonLabel: l10n.copyEmailAddress,
- secondButtonLabel: l10n.ok,
- firstButtonOnTap: () async {
- await Clipboard.setData(ClipboardData(text: toEmail));
- showShortToast(context, l10n.copied);
- },
- );
- }
|