diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 0de687d4e..381a73b8d 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -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
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index fcffe7d0a..2364db665 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -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",
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 7172f2013..92cb0a5a0 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -22,6 +22,18 @@
$(FLUTTER_BUILD_NAME)
CFBundleSignature
????
+ LSApplicationQueriesSchemes
+
+ googlegmail
+ x-dispatch
+ readdle-spark
+ airmail
+ ms-outlook
+ ymail
+ fastmail
+ superhuman
+ protonmail
+
CFBundleURLTypes
diff --git a/lib/ui/account/delete_account_page.dart b/lib/ui/account/delete_account_page.dart
index 30c99ae69..17177ece7 100644
--- a/lib/ui/account/delete_account_page.dart
+++ b/lib/ui/account/delete_account_page.dart
@@ -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(
diff --git a/lib/ui/payment/stripe_subscription_page.dart b/lib/ui/payment/stripe_subscription_page.dart
index 5b14edfc3..2d17449cd 100644
--- a/lib/ui/payment/stripe_subscription_page.dart
+++ b/lib/ui/payment/stripe_subscription_page.dart
@@ -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 {
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(
diff --git a/lib/ui/payment/subscription_page.dart b/lib/ui/payment/subscription_page.dart
index 4472d62da..f1e42bcb0 100644
--- a/lib/ui/payment/subscription_page.dart
+++ b/lib/ui/payment/subscription_page.dart
@@ -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 {
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(
diff --git a/lib/ui/settings/backup_section_widget.dart b/lib/ui/settings/backup_section_widget.dart
index d0746df3a..2f90e01cc 100644
--- a/lib/ui/settings/backup_section_widget.dart
+++ b/lib/ui/settings/backup_section_widget.dart
@@ -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 {
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 {
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",
+ );
}
},
),
diff --git a/lib/ui/settings/social_section_widget.dart b/lib/ui/settings/social_section_widget.dart
index 352da3ee3..7756622d7 100644
--- a/lib/ui/settings/social_section_widget.dart
+++ b/lib/ui/settings/social_section_widget.dart
@@ -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",
);
}
diff --git a/lib/ui/settings/support_section_widget.dart b/lib/ui/settings/support_section_widget.dart
index 6fa59c82c..5ba272303 100644
--- a/lib/ui/settings/support_section_widget.dart
+++ b/lib/ui/settings/support_section_widget.dart
@@ -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),
diff --git a/lib/utils/email_util.dart b/lib/utils/email_util.dart
index 48e2129e0..8b0b6d748 100644
--- a/lib/utils/email_util.dart
+++ b/lib/utils/email_util.dart
@@ -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 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 shareLogs(
sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2),
);
}
+
+Future 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 _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: [
+ 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);
+ },
+ )
+ ],
+ );
+ },
+ );
+}
diff --git a/pubspec.lock b/pubspec.lock
index 1fd47f4a7..7807d53ba 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -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:
diff --git a/pubspec.yaml b/pubspec.yaml
index f66304c44..eb46a33e1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -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