diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 0f17bb45..a26dd12c 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -30,6 +30,13 @@
+
+
+
+
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 359e46fc..595592c6 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -59,6 +59,17 @@
LaunchScreen
UIMainStoryboardFile
Main
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLSchemes
+
+ im.fluffychat
+
+
+
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
diff --git a/lib/app_config.dart b/lib/app_config.dart
index 2d214ee3..74628efc 100644
--- a/lib/app_config.dart
+++ b/lib/app_config.dart
@@ -15,6 +15,7 @@ abstract class AppConfig {
static String _privacyUrl = 'https://fluffychat.im/en/privacy.html';
static String get privacyUrl => _privacyUrl;
static const String appId = 'im.fluffychat.FluffyChat';
+ static const String appOpenUrlScheme = 'im.fluffychat';
static const String sourceCodeUrl = 'https://gitlab.com/famedly/fluffychat';
static const String supportUrl =
'https://gitlab.com/famedly/fluffychat/issues';
diff --git a/lib/config/routes.dart b/lib/config/routes.dart
index ab1e971b..1002989b 100644
--- a/lib/config/routes.dart
+++ b/lib/config/routes.dart
@@ -26,7 +26,6 @@ import 'package:fluffychat/views/settings_notifications.dart';
import 'package:fluffychat/views/settings_style.dart';
import 'package:fluffychat/views/sign_up.dart';
import 'package:fluffychat/views/sign_up_password.dart';
-import 'package:fluffychat/views/sso_web_view.dart';
import 'package:flutter/material.dart';
class FluffyRoutes {
@@ -47,8 +46,6 @@ class FluffyRoutes {
return ViewData(mainView: (_) => HomeserverPicker());
case 'login':
return ViewData(mainView: (_) => Login());
- case 'sso':
- return ViewData(mainView: (_) => SsoWebView());
case 'signup':
if (parts.length == 5 && parts[2] == 'password') {
return ViewData(
diff --git a/lib/views/homeserver_picker.dart b/lib/views/homeserver_picker.dart
index 553a4be7..7c2be369 100644
--- a/lib/views/homeserver_picker.dart
+++ b/lib/views/homeserver_picker.dart
@@ -1,3 +1,4 @@
+import 'dart:async';
import 'dart:math';
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
@@ -7,9 +8,14 @@ import 'package:fluffychat/components/matrix.dart';
import 'package:fluffychat/app_config.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flushbar/flushbar_helper.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter/material.dart';
+import 'package:future_loading_dialog/future_loading_dialog.dart';
+import 'package:receive_sharing_intent/receive_sharing_intent.dart';
+import 'package:url_launcher/url_launcher.dart';
import '../utils/localized_exception_extension.dart';
+import 'package:universal_html/prefer_universal/html.dart' as html;
class HomeserverPicker extends StatefulWidget {
@override
@@ -21,6 +27,46 @@ class _HomeserverPickerState extends State {
String _domain = AppConfig.defaultHomeserver;
final TextEditingController _controller =
TextEditingController(text: AppConfig.defaultHomeserver);
+ StreamSubscription _intentDataStreamSubscription;
+
+ void _loginWithToken(String token) {
+ if (token?.isEmpty ?? true) return;
+ showFutureLoadingDialog(
+ context: context,
+ future: () => Matrix.of(context).client.login(
+ type: AuthenticationTypes.token,
+ userIdentifierType: null,
+ token: token,
+ initialDeviceDisplayName: Matrix.of(context).clientName,
+ ),
+ );
+ }
+
+ void _processIncomingSharedText(String text) async {
+ if (text == null || !text.startsWith(AppConfig.appOpenUrlScheme)) return;
+ AdaptivePageLayout.of(context).popUntilIsFirst();
+ final token = Uri.parse(text).queryParameters['loginToken'];
+ _loginWithToken(token);
+ }
+
+ void _initReceiveSharingContent() {
+ if (!PlatformInfos.isMobile) return;
+ _intentDataStreamSubscription =
+ ReceiveSharingIntent.getTextStream().listen(_processIncomingSharedText);
+ ReceiveSharingIntent.getInitialText().then(_processIncomingSharedText);
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ _initReceiveSharingContent();
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ _intentDataStreamSubscription?.cancel();
+ }
void _checkHomeserverAction(BuildContext context) async {
try {
@@ -40,7 +86,11 @@ class _HomeserverPickerState extends State {
.pushNamed(AppConfig.enableRegistration ? '/signup' : '/login');
} else if (loginTypes.flows
.any((flow) => flow.type == AuthenticationTypes.sso)) {
- await AdaptivePageLayout.of(context).pushNamed('/sso');
+ final redirectUrl = kIsWeb
+ ? html.window.location.href
+ : '${Uri.encodeQueryComponent(AppConfig.appOpenUrlScheme.toLowerCase() + '://sso')}';
+ await launch(
+ '${Matrix.of(context).client.homeserver?.toString()}/_matrix/client/r0/login/sso/redirect?redirectUrl=$redirectUrl');
}
} on String catch (e) {
// ignore: unawaited_futures
@@ -62,6 +112,13 @@ class _HomeserverPickerState extends State {
final padding = EdgeInsets.symmetric(
horizontal: max((MediaQuery.of(context).size.width - 600) / 2, 0),
);
+ if (kIsWeb) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final token =
+ Uri.parse(html.window.location.href).queryParameters['loginToken'];
+ _loginWithToken(token);
+ });
+ }
return Scaffold(
appBar: AppBar(
title: DefaultAppBarSearchField(
diff --git a/lib/views/sso_web_view.dart b/lib/views/sso_web_view.dart
deleted file mode 100644
index 1d5f25a7..00000000
--- a/lib/views/sso_web_view.dart
+++ /dev/null
@@ -1,60 +0,0 @@
-import 'package:famedlysdk/famedlysdk.dart';
-import 'package:fluffychat/components/matrix.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_gen/gen_l10n/l10n.dart';
-import 'package:webview_flutter/webview_flutter.dart';
-
-import '../app_config.dart';
-
-class SsoWebView extends StatefulWidget {
- @override
- _SsoWebViewState createState() => _SsoWebViewState();
-}
-
-class _SsoWebViewState extends State {
- bool _loading = false;
- String _error;
-
- void _login(BuildContext context, String token) async {
- setState(() => _loading = true);
- try {
- await Matrix.of(context).client.login(
- type: AuthenticationTypes.token,
- userIdentifierType: null,
- token: token,
- initialDeviceDisplayName: Matrix.of(context).clientName,
- );
- } catch (e, s) {
- Logs().e('Login with token failed', e, s);
- setState(() => _error = e.toString());
- }
- }
-
- @override
- Widget build(BuildContext context) {
- final url =
- '${Matrix.of(context).client.homeserver?.toString()}/_matrix/client/r0/login/sso/redirect?redirectUrl=${Uri.encodeQueryComponent(AppConfig.appId.toLowerCase() + '://sso')}';
- return Scaffold(
- appBar: AppBar(
- title: Text(
- L10n.of(context).logInTo(Matrix.of(context).client.homeserver?.host ??
- L10n.of(context).oopsSomethingWentWrong),
- ),
- ),
- body: _error != null
- ? Center(child: Text(_error))
- : _loading
- ? Center(child: CircularProgressIndicator())
- : WebView(
- initialUrl: url,
- javascriptMode: JavascriptMode.unrestricted,
- onPageStarted: (url) {
- if (url.startsWith(AppConfig.appId.toLowerCase())) {
- _login(context,
- Uri.parse(url).queryParameters['loginToken']);
- }
- },
- ),
- );
- }
-}
diff --git a/pubspec.lock b/pubspec.lock
index e8ab3569..05fab1ed 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1271,13 +1271,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.3"
- webview_flutter:
- dependency: "direct main"
- description:
- name: webview_flutter
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.7"
win32:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index f78354e5..14cd4d35 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -35,7 +35,6 @@ dependencies:
path_provider: ^1.6.27
android_path_provider: ^0.1.1
permission_handler: ^5.0.1+1
- webview_flutter: ^1.0.7
share: ^0.6.5+4
flutter_secure_storage: ^3.3.5
http: ^0.12.2