diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index eaefe5c0e..ed1e2c0ee 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -49,6 +49,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- LSApplicationQueriesSchemes
-
- https
- http
-
+ LSApplicationQueriesSchemes
+
+ https
+ http
+
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
@@ -23,6 +23,17 @@
$(FLUTTER_BUILD_NAME)
CFBundleSignature
????
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLSchemes
+
+ ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)
+
+
+
CFBundleVersion
$(FLUTTER_BUILD_NUMBER)
LSRequiresIPhoneOS
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
new file mode 100644
index 000000000..352d0d88f
--- /dev/null
+++ b/ios/Runner/Runner.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.com.linagora.ios.teammail
+
+
+
diff --git a/ios/TeamMailShareExtension/Base.lproj/MainInterface.storyboard b/ios/TeamMailShareExtension/Base.lproj/MainInterface.storyboard
new file mode 100644
index 000000000..4c1e68d17
--- /dev/null
+++ b/ios/TeamMailShareExtension/Base.lproj/MainInterface.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/TeamMailShareExtension/Info.plist b/ios/TeamMailShareExtension/Info.plist
new file mode 100644
index 000000000..9a277dcf4
--- /dev/null
+++ b/ios/TeamMailShareExtension/Info.plist
@@ -0,0 +1,41 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ TeamMailShareExtension
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleVersion
+ 1
+ NSExtension
+
+ NSExtensionAttributes
+
+ NSExtensionActivationRule
+
+ NSExtensionActivationSupportsText
+
+ NSExtensionActivationSupportsWebURLWithMaxCount
+ 1
+
+
+ NSExtensionMainStoryboard
+ MainInterface
+ NSExtensionPointIdentifier
+ com.apple.share-services
+
+
+
\ No newline at end of file
diff --git a/ios/TeamMailShareExtension/ShareViewController.swift b/ios/TeamMailShareExtension/ShareViewController.swift
new file mode 100644
index 000000000..7331b153e
--- /dev/null
+++ b/ios/TeamMailShareExtension/ShareViewController.swift
@@ -0,0 +1,134 @@
+//
+// ShareViewController.swift
+// TeamMailShareExtension
+//
+// Created by dab.dev on 06/04/2022.
+//
+
+import UIKit
+import Social
+import MobileCoreServices
+
+class ShareViewController: SLComposeServiceViewController {
+
+ let hostAppBundleIdentifier = "com.linagora.ios.teammail"
+ let sharedKey = "ShareKey"
+ var sharedText: [String] = []
+ let textContentType = kUTTypeText as String
+ let urlContentType = kUTTypeURL as String
+
+ override func isContentValid() -> Bool {
+ return true
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad();
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
+ if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
+ if let contents = content.attachments {
+ for (index, attachment) in (contents).enumerated() {
+ if attachment.hasItemConformingToTypeIdentifier(textContentType) {
+ handleText(content: content, attachment: attachment, index: index)
+ } else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
+ handleUrl(content: content, attachment: attachment, index: index)
+ }
+ }
+ }
+ }
+ }
+
+ override func didSelectPost() {
+ print("didSelectPost");
+ }
+
+ override func configurationItems() -> [Any]! {
+ // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
+ return []
+ }
+
+ private func handleText (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
+ attachment.loadItem(forTypeIdentifier: textContentType, options: nil) { [weak self] data, error in
+
+ if error == nil, let item = data as? String, let this = self {
+
+ this.sharedText.append(item)
+
+ // If this is the last item, save imagesData in userDefaults and redirect to host app
+ if index == (content.attachments?.count)! - 1 {
+ let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
+ userDefaults?.set(this.sharedText, forKey: this.sharedKey)
+ userDefaults?.synchronize()
+ this.redirectToHostApp(type: .text)
+ }
+
+ } else {
+ self?.dismissWithError()
+ }
+ }
+ }
+
+ private func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
+ attachment.loadItem(forTypeIdentifier: urlContentType, options: nil) { [weak self] data, error in
+
+ if error == nil, let item = data as? URL, let this = self {
+
+ this.sharedText.append(item.absoluteString)
+
+ // If this is the last item, save imagesData in userDefaults and redirect to host app
+ if index == (content.attachments?.count)! - 1 {
+ let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
+ userDefaults?.set(this.sharedText, forKey: this.sharedKey)
+ userDefaults?.synchronize()
+ this.redirectToHostApp(type: .text)
+ }
+
+ } else {
+ self?.dismissWithError()
+ }
+ }
+ }
+
+ private func dismissWithError() {
+ print("[ERROR] Error loading data!")
+ let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)
+
+ let action = UIAlertAction(title: "Error", style: .cancel) { _ in
+ self.dismiss(animated: true, completion: nil)
+ }
+
+ alert.addAction(action)
+ present(alert, animated: true, completion: nil)
+ extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
+ }
+
+ private func redirectToHostApp(type: RedirectType) {
+ let url = URL(string: "ShareMedia://dataUrl=\(sharedKey)#\(type)")
+ var responder = self as UIResponder?
+ let selectorOpenURL = sel_registerName("openURL:")
+
+ while (responder != nil) {
+ if (responder?.responds(to: selectorOpenURL))! {
+ let _ = responder?.perform(selectorOpenURL, with: url)
+ }
+ responder = responder!.next
+ }
+ extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
+ }
+
+ enum RedirectType {
+ case media
+ case text
+ case file
+ }
+}
+
+extension Array {
+ subscript (safe index: UInt) -> Element? {
+ return Int(index) < count ? self[Int(index)] : nil
+ }
+}
diff --git a/ios/TeamMailShareExtension/TeamMailShareExtension.entitlements b/ios/TeamMailShareExtension/TeamMailShareExtension.entitlements
new file mode 100644
index 000000000..f30d887c7
--- /dev/null
+++ b/ios/TeamMailShareExtension/TeamMailShareExtension.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.com.linagora.ios.teammail
+
+
+
diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart
index 009cea97c..401d90a61 100644
--- a/lib/features/email/presentation/email_controller.dart
+++ b/lib/features/email/presentation/email_controller.dart
@@ -82,8 +82,7 @@ class EmailController extends BaseController {
);
@override
- void onReady() {
- super.onReady();
+ void onInit() {
mailboxDashBoardController.selectedEmail.listen((presentationEmail) {
log('EmailController::onReady(): ${presentationEmail.toString()}');
if (_currentEmailId != presentationEmail?.id) {
@@ -97,6 +96,7 @@ class EmailController extends BaseController {
}
}
});
+ super.onInit();
}
@override
diff --git a/lib/features/home/presentation/home_bindings.dart b/lib/features/home/presentation/home_bindings.dart
index 387363a50..d656af83e 100644
--- a/lib/features/home/presentation/home_bindings.dart
+++ b/lib/features/home/presentation/home_bindings.dart
@@ -10,6 +10,7 @@ import 'package:tmail_ui_user/features/cleanup/domain/usecases/cleanup_email_cac
import 'package:tmail_ui_user/features/home/presentation/home_controller.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_credential_interactor.dart';
import 'package:tmail_ui_user/features/thread/data/local/email_cache_manager.dart';
+import 'package:tmail_ui_user/main/utils/email_receive_manager.dart';
class HomeBindings extends BaseBindings {
@@ -25,6 +26,7 @@ class HomeBindings extends BaseBindings {
Get.find(),
Get.find(),
Get.find(),
+ Get.find(),
));
}
diff --git a/lib/features/home/presentation/home_controller.dart b/lib/features/home/presentation/home_controller.dart
index effa7cc90..1a00d077d 100644
--- a/lib/features/home/presentation/home_controller.dart
+++ b/lib/features/home/presentation/home_controller.dart
@@ -2,32 +2,43 @@ import 'package:core/core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:get/get.dart';
+import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:tmail_ui_user/features/cleanup/domain/model/cleanup_rule.dart';
import 'package:tmail_ui_user/features/cleanup/domain/usecases/cleanup_email_cache_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_credential_state.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_credential_interactor.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
+import 'package:tmail_ui_user/main/utils/email_receive_manager.dart';
class HomeController extends GetxController {
final GetCredentialInteractor _getCredentialInteractor;
final DynamicUrlInterceptors _dynamicUrlInterceptors;
final AuthorizationInterceptors _authorizationInterceptors;
final CleanupEmailCacheInteractor _cleanupEmailCacheInteractor;
+ final EmailReceiveManager _emailReceiveManager;
HomeController(
this._getCredentialInteractor,
this._dynamicUrlInterceptors,
this._authorizationInterceptors,
this._cleanupEmailCacheInteractor,
+ this._emailReceiveManager,
);
+ @override
+ void onInit() {
+ log('HomeController::onInit(): ');
+ if (!kIsWeb) {
+ _initFlutterDownloader();
+ _registerReceivingSharingIntent();
+ }
+ super.onInit();
+ }
+
@override
void onReady() {
super.onReady();
- if (!kIsWeb) {
- _initFlutterDownloader();
- }
_cleanupEmailCache();
}
@@ -51,6 +62,16 @@ class HomeController extends GetxController {
(success) => success is GetCredentialViewState ? _goToMailbox(success) : _goToLogin()));
}
+ void _registerReceivingSharingIntent() {
+ _emailReceiveManager.receivingSharingStream.listen((uri) {
+ log('HomeController::onReady(): Received Email: ${uri.toString()}');
+ if (uri != null) {
+ log('HomeController::onReady(): Address: ${uri.path}');
+ _emailReceiveManager.setPendingEmailAddress(EmailAddress(null, uri.path));
+ }
+ });
+ }
+
void _goToLogin() {
pushAndPop(AppRoutes.LOGIN);
}
diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart
index 55de15634..de95b1c93 100644
--- a/lib/features/mailbox/presentation/mailbox_controller.dart
+++ b/lib/features/mailbox/presentation/mailbox_controller.dart
@@ -104,15 +104,19 @@ class MailboxController extends BaseMailboxController {
) : super(treeBuilder);
@override
- void onReady() {
- super.onReady();
+ void onInit() {
mailboxDashBoardController.accountId.listen((accountId) {
log('MailboxController::onReady(): accountId: $accountId');
if (accountId != null) {
getAllMailboxAction(accountId);
}
});
+ super.onInit();
+ }
+ @override
+ void onReady() {
+ super.onReady();
mailboxDashBoardController.viewState.listen((state) {
state.fold(
(failure) {
diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart
index b2e1f5a52..f5ffff77b 100644
--- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart
+++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart
@@ -44,6 +44,7 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:tmail_ui_user/main/routes/router_arguments.dart';
+import 'package:tmail_ui_user/main/utils/email_receive_manager.dart';
class MailboxDashBoardController extends ReloadableController {
@@ -55,6 +56,7 @@ class MailboxDashBoardController extends ReloadableController {
final CachingManager _cachingManager = Get.find();
final Connectivity _connectivity = Get.find();
final ResponsiveUtils _responsiveUtils = Get.find();
+ final EmailReceiveManager _emailReceiveManager = Get.find();
final MoveToTrashInteractor _moveToTrashInteractor;
final MoveToMailboxInteractor _moveToMailboxInteractor;
@@ -79,6 +81,7 @@ class MailboxDashBoardController extends ReloadableController {
FocusNode searchFocus = FocusNode();
RouterArguments? routerArguments;
late StreamSubscription _connectivityStreamSubscription;
+ late StreamSubscription _emailReceiveManagerStreamSubscription;
MailboxDashBoardController(
this._moveToTrashInteractor,
@@ -88,8 +91,9 @@ class MailboxDashBoardController extends ReloadableController {
@override
void onInit() {
- super.onInit();
_registerNetworkConnectivityState();
+ _registerPendingEmailAddress();
+ super.onInit();
}
@override
@@ -178,23 +182,36 @@ class MailboxDashBoardController extends ReloadableController {
});
}
+ void _registerPendingEmailAddress() {
+ _emailReceiveManagerStreamSubscription =
+ _emailReceiveManager.pendingEmailAddressInfo.stream.listen((emailAddress) {
+ log('MailboxDashBoardController::_registerPendingEmailAddress(): ${emailAddress?.email}');
+ if (emailAddress != null && emailAddress.email?.isNotEmpty == true) {
+ _emailReceiveManager.clearPendingEmailAddress();
+ final arguments = ComposerArguments(
+ emailActionType: EmailActionType.composeFromEmailAddress,
+ emailAddress: emailAddress,
+ mailboxRole: selectedMailbox.value?.role);
+ goToComposer(arguments);
+ }
+ });
+ }
+
void _getUserProfile() async {
consumeState(_getUserProfileInteractor.execute());
}
void _setSessionCurrent() {
- Future.delayed(const Duration(milliseconds: 100), () {
- final arguments = Get.arguments;
- log('MailboxDashBoardController::_setSessionCurrent(): arguments = $arguments');
- if (arguments is Session) {
- sessionCurrent = Get.arguments as Session;
- accountId.value = sessionCurrent?.accounts.keys.first;
- } else {
- if (kIsWeb) {
- reload();
- }
+ final arguments = Get.arguments;
+ log('MailboxDashBoardController::_setSessionCurrent(): arguments = $arguments');
+ if (arguments is Session) {
+ sessionCurrent = arguments;
+ accountId.value = sessionCurrent?.accounts.keys.first;
+ } else {
+ if (kIsWeb) {
+ reload();
}
- });
+ }
}
Future _initPackageInfo() async {
@@ -393,6 +410,16 @@ class MailboxDashBoardController extends ReloadableController {
}
}
+ void goToComposer(ComposerArguments arguments) {
+ if (kIsWeb) {
+ if (dashBoardAction != DashBoardAction.compose) {
+ dispatchDashBoardAction(DashBoardAction.compose, arguments: arguments);
+ }
+ } else {
+ push(AppRoutes.COMPOSER, arguments: arguments);
+ }
+ }
+
void _deleteCredential() async {
await _deleteCredentialInteractor.execute();
}
@@ -409,6 +436,8 @@ class MailboxDashBoardController extends ReloadableController {
@override
void onClose() {
+ _emailReceiveManager.closeEmailReceiveManagerStream();
+ _emailReceiveManagerStreamSubscription.cancel();
_connectivityStreamSubscription.cancel();
searchInputController.dispose();
searchFocus.dispose();
diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart
index 40d4fef3f..f9c54ae45 100644
--- a/lib/features/thread/presentation/thread_controller.dart
+++ b/lib/features/thread/presentation/thread_controller.dart
@@ -127,12 +127,6 @@ class ThreadController extends BaseController {
@override
void onInit() {
super.onInit();
- dispatchState(Right(LoadingState()));
- }
-
- @override
- void onReady() {
- super.onReady();
mailboxDashBoardController.selectedMailbox.listen((selectedMailbox) {
if (_currentMailboxId != selectedMailbox?.id) {
log('ThreadController::onReady(): selectMailbox: ${selectedMailbox?.name?.name}(${selectedMailbox?.id})');
@@ -141,7 +135,13 @@ class ThreadController extends BaseController {
_getAllEmail();
}
});
+ super.onInit();
+ dispatchState(Right(LoadingState()));
+ }
+ @override
+ void onReady() {
+ super.onReady();
mailboxDashBoardController.viewState.listen((state) {
state.map((success) {
log('ThreadController::onReady(): ${success.runtimeType}');
@@ -262,7 +262,6 @@ class ThreadController extends BaseController {
canLoadMore = true;
disableSearch();
cancelSelectEmail();
- listEmailController.animateTo(0, duration: Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
mailboxDashBoardController.dispatchRoute(AppRoutes.THREAD);
}
@@ -270,6 +269,9 @@ class ThreadController extends BaseController {
log('ThreadController::_getAllEmailSuccess(): ${success.emailList.length}');
_currentEmailState = success.currentEmailState;
emailList.value = success.emailList;
+ if (listEmailController.hasClients) {
+ listEmailController.animateTo(0, duration: Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
+ }
}
void _refreshChangesAllEmailSuccess(RefreshChangesAllEmailSuccess success) {
diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart
index 0712989ca..84f3d85ed 100644
--- a/lib/main/bindings/core/core_bindings.dart
+++ b/lib/main/bindings/core/core_bindings.dart
@@ -4,6 +4,7 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart';
+import 'package:tmail_ui_user/main/utils/email_receive_manager.dart';
class CoreBindings extends Bindings {
@@ -16,6 +17,7 @@ class CoreBindings extends Bindings {
_bindingTransformer();
_bindingToast();
_bindingDeviceManager();
+ _bindingReceivingSharingStream();
}
void _bindingAppImagePaths() {
@@ -47,4 +49,8 @@ class CoreBindings extends Bindings {
Get.put(DeviceInfoPlugin());
Get.put(DeviceManager(Get.find()));
}
+
+ void _bindingReceivingSharingStream() {
+ Get.put(EmailReceiveManager());
+ }
}
\ No newline at end of file
diff --git a/lib/main/utils/email_receive_manager.dart b/lib/main/utils/email_receive_manager.dart
new file mode 100644
index 000000000..902118d8b
--- /dev/null
+++ b/lib/main/utils/email_receive_manager.dart
@@ -0,0 +1,40 @@
+
+import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
+import 'package:receive_sharing_intent/receive_sharing_intent.dart';
+import 'package:rxdart/rxdart.dart';
+
+class EmailReceiveManager {
+
+ BehaviorSubject _pendingEmailAddressInfo = BehaviorSubject.seeded(null);
+ BehaviorSubject get pendingEmailAddressInfo => _pendingEmailAddressInfo;
+
+ Stream get receivingSharingStream {
+ return Rx.merge([
+ Stream.fromFuture(ReceiveSharingIntent.getInitialTextAsUri()),
+ ReceiveSharingIntent.getTextStreamAsUri()
+ ]);
+ }
+ Stream> get receivingSharingStream2 {
+ return Rx.merge([
+ Stream.fromFuture(ReceiveSharingIntent.getInitialMedia()),
+ ReceiveSharingIntent.getMediaStream()
+ ]);
+ }
+
+ void setPendingEmailAddress(EmailAddress emailAddress) async {
+ clearPendingEmailAddress();
+ _pendingEmailAddressInfo.add(emailAddress);
+ }
+
+ void clearPendingEmailAddress() {
+ if(_pendingEmailAddressInfo.isClosed) {
+ _pendingEmailAddressInfo = BehaviorSubject.seeded(null);
+ } else {
+ _pendingEmailAddressInfo.add(null);
+ }
+ }
+
+ void closeEmailReceiveManagerStream() {
+ _pendingEmailAddressInfo.close();
+ }
+}
\ No newline at end of file
diff --git a/pubspec.yaml b/pubspec.yaml
index 406fe5ab0..1c106957f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -141,6 +141,8 @@ dependencies:
package_info_plus: 1.4.1
+ receive_sharing_intent: 1.4.5
+
dev_dependencies:
flutter_test:
sdk: flutter