Merge pull request #390 from ente-io/ios_fix_send_email

Fix email action for iOS
This commit is contained in:
Manav 2022-07-11 19:01:10 +05:30 committed by GitHub
commit 4d2c9101fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 179 additions and 47 deletions

View file

@ -139,6 +139,8 @@ PODS:
- nanopb/encode (2.30908.0)
- open_file (0.0.1):
- Flutter
- open_mail_app (0.0.1):
- Flutter
- OrderedSet (5.0.0)
- package_info_plus (0.4.5):
- Flutter
@ -223,6 +225,7 @@ DEPENDENCIES:
- motionphoto (from `.symlinks/plugins/motionphoto/ios`)
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
- open_file (from `.symlinks/plugins/open_file/ios`)
- open_mail_app (from `.symlinks/plugins/open_mail_app/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- permission_handler (from `.symlinks/plugins/permission_handler/ios`)
@ -315,6 +318,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/move_to_background/ios"
open_file:
:path: ".symlinks/plugins/open_file/ios"
open_mail_app:
:path: ".symlinks/plugins/open_mail_app/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_ios:
@ -387,6 +392,7 @@ SPEC CHECKSUMS:
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d
open_mail_app: 794172f6a22cd16319d3ddaf45e945b2f74952b0
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02

View file

@ -299,6 +299,7 @@
"${BUILT_PRODUCTS_DIR}/move_to_background/move_to_background.framework",
"${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
"${BUILT_PRODUCTS_DIR}/open_file/open_file.framework",
"${BUILT_PRODUCTS_DIR}/open_mail_app/open_mail_app.framework",
"${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework",
"${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework",
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
@ -360,6 +361,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/move_to_background.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_file.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_mail_app.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",

View file

@ -22,6 +22,18 @@
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>googlegmail</string>
<string>x-dispatch</string>
<string>readdle-spark</string>
<string>airmail</string>
<string>ms-outlook</string>
<string>ymail</string>
<string>fastmail</string>
<string>superhuman</string>
<string>protonmail</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>

View file

@ -1,7 +1,6 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/models/delete_account.dart';
@ -11,8 +10,8 @@ import 'package:photos/ui/common/gradient_button.dart';
import 'package:photos/ui/tools/app_lock.dart';
import 'package:photos/utils/auth_util.dart';
import 'package:photos/utils/crypto_util.dart';
import 'package:photos/utils/email_util.dart';
import 'package:photos/utils/toast_util.dart';
import 'package:url_launcher/url_launcher.dart';
class DeleteAccountPage extends StatelessWidget {
const DeleteAccountPage({
@ -81,11 +80,10 @@ class DeleteAccountPage extends StatelessWidget {
paddingValue: 4,
iconData: Icons.check,
onTap: () async {
await launchUrl(
Uri(
scheme: "mailto",
path: 'feedback@ente.io',
),
await sendEmail(
context,
to: 'feedback@ente.io',
subject: '[Feedback]',
);
},
),
@ -222,15 +220,11 @@ class DeleteAccountPage extends StatelessWidget {
),
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop('dialog');
try {
final Email email = Email(
recipients: ['account-deletion@ente.io'],
isHTML: false,
);
await FlutterEmailSender.send(email);
} catch (e) {
launch("mailto:account-deletion@ente.io");
}
await sendEmail(
context,
to: 'account-deletion@ente.io',
subject: '[Delete account]',
);
},
),
TextButton(

View file

@ -21,7 +21,7 @@ import 'package:photos/ui/payment/subscription_plan_widget.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/toast_util.dart';
import 'package:step_progress_indicator/step_progress_indicator.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
class StripeSubscriptionPage extends StatefulWidget {
final bool isOnboarding;
@ -224,14 +224,14 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
await _launchStripePortal();
break;
case kPlayStore:
launch(
launchUrlString(
"https://play.google.com/store/account/subscriptions?sku=" +
_currentSubscription.productID +
"&package=io.ente.photos",
);
break;
case kAppStore:
launch("https://apps.apple.com/account/billing");
launchUrlString("https://apps.apple.com/account/billing");
break;
default:
_logger.severe(

View file

@ -20,7 +20,7 @@ import 'package:photos/ui/payment/subscription_common_widgets.dart';
import 'package:photos/ui/payment/subscription_plan_widget.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/toast_util.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SubscriptionPage extends StatefulWidget {
final bool isOnboarding;
@ -213,13 +213,13 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
return;
}
if (Platform.isAndroid) {
launch(
launchUrlString(
"https://play.google.com/store/account/subscriptions?sku=" +
_currentSubscription.productID +
"&package=io.ente.photos",
);
} else {
launch("https://apps.apple.com/account/billing");
launchUrlString("https://apps.apple.com/account/billing");
}
},
child: Container(

View file

@ -18,7 +18,7 @@ import 'package:photos/utils/data_util.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/navigation_util.dart';
import 'package:photos/utils/toast_util.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
class BackupSectionWidget extends StatefulWidget {
const BackupSectionWidget({Key key}) : super(key: key);
@ -229,11 +229,13 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
Navigator.of(context, rootNavigator: true).pop('dialog');
// TODO: Replace with https://pub.dev/packages/in_app_review
if (Platform.isAndroid) {
launch(
launchUrlString(
"https://play.google.com/store/apps/details?id=io.ente.photos",
);
} else {
launch("https://apps.apple.com/in/app/ente-photos/id1542026904");
launchUrlString(
"https://apps.apple.com/in/app/ente-photos/id1542026904",
);
}
},
),
@ -289,11 +291,13 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
Navigator.of(context, rootNavigator: true).pop('dialog');
// TODO: Replace with https://pub.dev/packages/in_app_review
if (Platform.isAndroid) {
launch(
launchUrlString(
"https://play.google.com/store/apps/details?id=io.ente.photos",
);
} else {
launch("https://apps.apple.com/in/app/ente-photos/id1542026904");
launchUrlString(
"https://apps.apple.com/in/app/ente-photos/id1542026904",
);
}
},
),

View file

@ -6,7 +6,7 @@ import 'package:photos/services/update_service.dart';
import 'package:photos/ui/settings/common_settings.dart';
import 'package:photos/ui/settings/settings_section_title.dart';
import 'package:photos/ui/settings/settings_text_item.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SocialSectionWidget extends StatelessWidget {
const SocialSectionWidget({Key key}) : super(key: key);
@ -26,7 +26,7 @@ class SocialSectionWidget extends StatelessWidget {
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
launch("https://twitter.com/enteio");
launchUrlString("https://twitter.com/enteio");
},
child:
const SettingsTextItem(text: "Twitter", icon: Icons.navigate_next),
@ -35,7 +35,7 @@ class SocialSectionWidget extends StatelessWidget {
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
launch("https://ente.io/discord");
launchUrlString("https://ente.io/discord");
},
child:
const SettingsTextItem(text: "Discord", icon: Icons.navigate_next),
@ -44,7 +44,7 @@ class SocialSectionWidget extends StatelessWidget {
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
launch("https://reddit.com/r/enteio");
launchUrlString("https://reddit.com/r/enteio");
},
child:
const SettingsTextItem(text: "Reddit", icon: Icons.navigate_next),
@ -58,11 +58,11 @@ class SocialSectionWidget extends StatelessWidget {
behavior: HitTestBehavior.translucent,
onTap: () {
if (Platform.isAndroid) {
launch(
launchUrlString(
"https://play.google.com/store/apps/details?id=io.ente.photos",
);
} else {
launch(
launchUrlString(
"https://apps.apple.com/in/app/ente-photos/id1542026904",
);
}

View file

@ -2,16 +2,13 @@ import 'dart:io';
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/ui/common/web_page.dart';
import 'package:photos/ui/settings/common_settings.dart';
import 'package:photos/ui/settings/settings_section_title.dart';
import 'package:photos/ui/settings/settings_text_item.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/email_util.dart';
import 'package:url_launcher/url_launcher.dart';
class SupportSectionWidget extends StatelessWidget {
const SupportSectionWidget({Key key}) : super(key: key);
@ -34,16 +31,7 @@ class SupportSectionWidget extends StatelessWidget {
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
try {
final Uri emailLaunchUri = Uri(
scheme: 'mailto',
path: kSupportEmail,
);
launchUrl(emailLaunchUri);
} catch (e) {
Logger("SupportSection").severe(e);
showErrorDialog(context, "", "Please email us at $kSupportEmail");
}
await sendEmail(context, to: kSupportEmail);
},
child:
const SettingsTextItem(text: "Email", icon: Icons.navigate_next),

View file

@ -2,10 +2,13 @@ import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:email_validator/email_validator.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:logging/logging.dart';
import 'package:open_mail_app/open_mail_app.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/error-reporting/super_logging.dart';
@ -13,7 +16,9 @@ import 'package:photos/ente_theme_data.dart';
import 'package:photos/ui/common/dialogs.dart';
import 'package:photos/ui/tools/debug/log_file_viewer.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/toast_util.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
final Logger _logger = Logger('email_util');
@ -163,7 +168,7 @@ Future<void> shareLogs(
"Email logs",
"Please send the logs to $toEmail",
firstAction: "Copy email",
secondAction: "Send",
secondAction: "Export logs",
);
if (result != null && result == DialogUserChoice.firstChoice) {
await Clipboard.setData(ClipboardData(text: toEmail));
@ -174,3 +179,115 @@ Future<void> shareLogs(
sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2),
);
}
Future<void> sendEmail(
BuildContext context, {
@required String to,
String subject,
String body,
}) async {
try {
String clientDebugInfo = await _clientInfo();
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=${email.subject}&body=${email.body}',
);
if (await canLaunchUrl(params)) {
await launchUrl(params);
} else {
// this will trigger _showNoMailAppsDialog
throw Exception('Could not launch ${params.toString()}');
}
} else {
OpenMailAppResult result = await OpenMailApp.composeNewEmailInMailApp(
nativePickerTitle: 'Select email app',
emailContent: email,
);
if (!result.didOpen && !result.canOpen) {
_showNoMailAppsDialog(context, to);
} else if (!result.didOpen && result.canOpen) {
showCupertinoModalPopup(
context: context,
builder: (_) => CupertinoActionSheet(
title: Text("Select mail app \n $to"),
actions: [
for (var app in result.options)
CupertinoActionSheetAction(
child: Text(app.name),
onPressed: () {
final content = email;
if (content != null) {
OpenMailApp.composeNewEmailInSpecificMailApp(
mailApp: app,
emailContent: content,
);
} else {
OpenMailApp.openSpecificMailApp(app);
}
Navigator.pop(context);
},
),
],
cancelButton: CupertinoActionSheetAction(
child: const Text("Cancel"),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
),
),
);
}
}
} catch (e) {
_logger.severe("Failed to send email to $to", e);
_showNoMailAppsDialog(context, to);
}
}
Future<String> _clientInfo() async {
final packageInfo = await PackageInfo.fromPlatform();
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) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Please email us at $toEmail'),
actions: <Widget>[
TextButton(
child: const Text("Copy email"),
onPressed: () async {
await Clipboard.setData(ClipboardData(text: toEmail));
showShortToast(context, 'Copied');
},
),
TextButton(
child: const Text("OK"),
onPressed: () {
Navigator.pop(context);
},
)
],
);
},
);
}

View file

@ -809,6 +809,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.1"
open_mail_app:
dependency: "direct main"
description:
name: open_mail_app
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.5"
package_info_plus:
dependency: "direct main"
description:

View file

@ -82,7 +82,9 @@ dependencies:
motionphoto:
git: "https://github.com/ente-io/motionphoto.git"
move_to_background: ^1.0.2
open_file: ^3.2.1
open_mail_app: ^0.4.5
package_info_plus: ^1.0.1
page_transition: ^2.0.2
path_provider: ^2.0.1