diff --git a/lib/app_flavored.dart b/lib/app_flavored.dart index 907803c..759f132 100644 --- a/lib/app_flavored.dart +++ b/lib/app_flavored.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:humhub/flavored/models/humhub.f.dart'; +import 'package:humhub/flavored/util/intent_plugin.f.dart'; import 'package:humhub/flavored/util/router.f.dart'; -import 'package:humhub/util/intent/intent_plugin.dart'; import 'package:humhub/util/loading_provider.dart'; import 'package:humhub/util/notifications/plugin.dart'; import 'package:humhub/util/override_locale.dart'; @@ -21,7 +21,7 @@ class FlavoredAppState extends ConsumerState { @override Widget build(BuildContext context) { SecureStorageService.clearSecureStorageOnReinstall(); - return IntentPlugin( + return IntentPluginF( child: NotificationPlugin( child: PushPlugin( child: OverrideLocale( diff --git a/lib/flavored/util/intent_plugin.f.dart b/lib/flavored/util/intent_plugin.f.dart new file mode 100644 index 0000000..240b2a9 --- /dev/null +++ b/lib/flavored/util/intent_plugin.f.dart @@ -0,0 +1,137 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:humhub/flavored/util/router.f.dart'; +import 'package:humhub/flavored/web_view.f.dart'; +import 'package:humhub/util/loading_provider.dart'; +import 'package:loggy/loggy.dart'; +import 'package:receive_sharing_intent/receive_sharing_intent.dart'; +import 'package:uni_links/uni_links.dart'; + +bool _initialUriIsHandled = false; + +class IntentPluginF extends ConsumerStatefulWidget { + final Widget child; + + const IntentPluginF({ + Key? key, + required this.child, + }) : super(key: key); + + @override + IntentPluginFState createState() => IntentPluginFState(); +} + +class IntentPluginFState extends ConsumerState { + StreamSubscription? intentDataStreamSubscription; + List? sharedFiles; + Object? _err; + Uri? _initialUri; + Uri? _latestUri; + StreamSubscription? _sub; + + @override + void initState() { + logInfo([_err, _initialUri, _latestUri, _sub]); + super.initState(); + intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream().listen((List value) { + setState(() { + sharedFiles = value; + }); + }); + + // For sharing images coming from outside the app while the app is closed + ReceiveSharingIntent.getInitialMedia().then((List value) { + setState(() { + sharedFiles = value; + }); + }); + _handleInitialUri(); + _handleIncomingLinks(); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } + + /// Handle incoming links - the ones that the app will recieve from the OS + /// while already started. + Future _handleIncomingLinks() async { + if (!kIsWeb) { + // It will handle app links while the app is already started - be it in + // the foreground or in the background. + _sub = uriLinkStream.listen((Uri? uri) async { + if (!mounted) return; + _latestUri = uri; + String? redirectUrl = uri?.toString(); + if (redirectUrl != null && navigatorKeyF.currentState != null) { + tryNavigateWithOpener(redirectUrl); + } + _err = null; + }, onError: (err) { + if (kDebugMode) { + print(err); + } + }); + } + } + + /// Handle the initial Uri - the one the app was started with + /// + /// **ATTENTION**: `getInitialLink`/`getInitialUri` should be handled + /// ONLY ONCE in your app's lifetime, since it is not meant to change + /// throughout your app's life. + /// + /// We handle all exceptions, since it is called from initState. + Future _handleInitialUri() async { + // In this example app this is an almost useless guard, but it is here to + // show we are not going to call getInitialUri multiple times, even if this + // was a widget that will be disposed of (ex. a navigation route change). + + if (!_initialUriIsHandled) { + _initialUriIsHandled = true; + try { + final uri = await getInitialUri(); + if (uri == null || !mounted) return; + setState(() => _initialUri = uri); + if (!mounted) { + return; + } + _latestUri = uri; + String? redirectUrl = uri.queryParameters['url']; + if (redirectUrl != null && navigatorKeyF.currentState != null) { + tryNavigateWithOpener(redirectUrl); + } else { + if (redirectUrl != null) { + navigatorKeyF.currentState!.pushNamed(WebViewF.path, arguments: redirectUrl); + return; + } + } + } on PlatformException { + // Platform messages may fail but we ignore the exception + logError('Failed to get initial uri'); + } on FormatException catch (err) { + if (!mounted) return; + logError('Malformed initial uri'); + setState(() => _err = err); + } + } + } + + Future tryNavigateWithOpener(String redirectUrl) async { + LoadingProvider.of(ref).showLoading(); + bool isNewRouteSameAsCurrent = false; + navigatorKeyF.currentState!.popUntil((route) { + if (route.settings.name == WebViewF.path) { + isNewRouteSameAsCurrent = true; + } + return true; + }); + navigatorKeyF.currentState!.pushNamed(WebViewF.path, arguments: redirectUrl); + return isNewRouteSameAsCurrent; + } +} diff --git a/lib/pages/opener.dart b/lib/pages/opener.dart index b0d50be..028283c 100644 --- a/lib/pages/opener.dart +++ b/lib/pages/opener.dart @@ -246,7 +246,7 @@ class OpenerState extends ConsumerState with SingleTickerProviderStateMi await controlLer.initHumHub(); if (controlLer.allOk) { ref.read(humHubProvider).getInstance().then((value) { - Navigator.pushNamed(ref.context, WebViewApp.path, arguments: value.manifest); + Navigator.pushNamed(ref.context, WebView.path, arguments: value.manifest); }); } } diff --git a/lib/pages/web_view.dart b/lib/pages/web_view.dart index 3e18e07..1be265e 100644 --- a/lib/pages/web_view.dart +++ b/lib/pages/web_view.dart @@ -28,15 +28,15 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../util/web_view_global_controller.dart'; -class WebViewApp extends ConsumerStatefulWidget { - const WebViewApp({super.key}); +class WebView extends ConsumerStatefulWidget { + const WebView({super.key}); static const String path = '/web_view'; @override WebViewAppState createState() => WebViewAppState(); } -class WebViewAppState extends ConsumerState { +class WebViewAppState extends ConsumerState { late AuthInAppBrowser authBrowser; late Manifest manifest; late URLRequest _initialRequest; diff --git a/lib/util/intent/intent_plugin.dart b/lib/util/intent/intent_plugin.dart index 7b98b92..84668ca 100644 --- a/lib/util/intent/intent_plugin.dart +++ b/lib/util/intent/intent_plugin.dart @@ -110,7 +110,7 @@ class IntentPluginState extends ConsumerState { if (redirectUrl != null) { UniversalOpenerController opener = UniversalOpenerController(url: redirectUrl); await opener.initHumHub(); - navigatorKey.currentState!.pushNamed(WebViewApp.path, arguments: opener); + navigatorKey.currentState!.pushNamed(WebView.path, arguments: opener); return; } } @@ -129,7 +129,7 @@ class IntentPluginState extends ConsumerState { LoadingProvider.of(ref).showLoading(); bool isNewRouteSameAsCurrent = false; navigatorKey.currentState!.popUntil((route) { - if (route.settings.name == WebViewApp.path) { + if (route.settings.name == WebView.path) { isNewRouteSameAsCurrent = true; } return true; @@ -138,7 +138,7 @@ class IntentPluginState extends ConsumerState { await opener.initHumHub(); // Always pop the current instance and init the new one. LoadingProvider.of(ref).dismissAll(); - navigatorKey.currentState!.pushNamed(WebViewApp.path, arguments: opener); + navigatorKey.currentState!.pushNamed(WebView.path, arguments: opener); return isNewRouteSameAsCurrent; } } diff --git a/lib/util/notifications/channel.dart b/lib/util/notifications/channel.dart index 923d8bc..a9d3626 100644 --- a/lib/util/notifications/channel.dart +++ b/lib/util/notifications/channel.dart @@ -17,13 +17,13 @@ class NotificationChannel { this.description = 'These notifications are redirect the user to specific url in a payload.'}); /// If the WebView is not opened yet or the app is not running the onTap will wake up the app or redirect to the WebView. - /// If app is already running in WebView mode then the state of [WebViewApp] will be updated with new url. + /// If app is already running in WebView mode then the state of [WebView] will be updated with new url. /// Future onTap(String? payload) async { if (payload != null && navigatorKey.currentState != null) { bool isNewRouteSameAsCurrent = false; navigatorKey.currentState!.popUntil((route) { - if (route.settings.name == WebViewApp.path) { + if (route.settings.name == WebView.path) { isNewRouteSameAsCurrent = true; } return true; @@ -31,10 +31,10 @@ class NotificationChannel { UniversalOpenerController opener = UniversalOpenerController(url: payload); await opener.initHumHub(); if (isNewRouteSameAsCurrent) { - navigatorKey.currentState!.pushNamed(WebViewApp.path, arguments: opener); + navigatorKey.currentState!.pushNamed(WebView.path, arguments: opener); return; } - navigatorKey.currentState!.pushNamed(WebViewApp.path, arguments: opener); + navigatorKey.currentState!.pushNamed(WebView.path, arguments: opener); } else { if (payload != null) { InitFromPush.setPayload(payload); diff --git a/lib/util/router.dart b/lib/util/router.dart index 8fc58ce..0d69566 100644 --- a/lib/util/router.dart +++ b/lib/util/router.dart @@ -34,7 +34,7 @@ class MyRouter { static var routes = { Opener.path: (context) => const Opener(), - WebViewApp.path: (context) => const WebViewApp(), + WebView.path: (context) => const WebView(), '/help': (context) => Platform.isAndroid ? const HelpAndroid() : const HelpIos(), }; @@ -46,9 +46,9 @@ class MyRouter { initRoute = Opener.path; return Opener.path; case RedirectAction.webView: - initRoute = WebViewApp.path; + initRoute = WebView.path; initParams = humhub.manifest; - return WebViewApp.path; + return WebView.path; } } }