feat: open appflowy from admin web (#4047)

* feat: open appflowy from admin web

* feat: add loading indicator
This commit is contained in:
Nathan.fooo
2023-11-29 12:55:13 -08:00
committed by GitHub
parent 8665a25b39
commit 64aa2ba7e4
13 changed files with 259 additions and 126 deletions

View File

@ -8,6 +8,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/ser
import 'package:appflowy/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart';
import 'package:appflowy/plugins/trash/application/prelude.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart';
import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
@ -57,6 +58,15 @@ class DependencyResolver {
Future<void> _resolveCloudDeps(GetIt getIt) async {
final env = await AppFlowyCloudSharedEnv.fromEnv();
getIt.registerFactory<AppFlowyCloudSharedEnv>(() => env);
if (isAppFlowyCloudEnabled) {
getIt.registerSingleton(
AppFlowyCloudDeepLink(),
dispose: (obj) async {
await obj.dispose();
},
);
}
}
void _resolveCommonService(

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/startup/tasks/memory_leak_detector.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy_backend/appflowy_backend.dart';
@ -94,8 +95,8 @@ class FlowyRunner {
// ignore in test mode
if (!mode.isUnitTest) ...[
const HotKeyTask(),
InitSupabaseTask(),
InitAppFlowyCloudTask(),
if (isSupabaseEnabled) InitSupabaseTask(),
if (isAppFlowyCloudEnabled) InitAppFlowyCloudTask(),
const InitAppWidgetTask(),
const InitPlatformServiceTask(),
],

View File

@ -156,6 +156,7 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
}
class AppGlobals {
// static GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey = GlobalKey();
static GlobalKey<NavigatorState> rootNavKey = GlobalKey();
static NavigatorState get nav => rootNavKey.currentState!;
}

View File

@ -2,10 +2,138 @@ import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/app_widget.dart';
import 'package:appflowy/startup/tasks/supabase_task.dart';
import 'package:appflowy/user/application/auth/auth_error.dart';
import 'package:appflowy/user/application/auth/device_id.dart';
import 'package:appflowy/user/application/user_auth_listener.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_backend/log.dart';
import 'package:flutter/material.dart';
import 'package:url_protocol/url_protocol.dart';
import 'dart:async';
import 'package:app_links/app_links.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:dartz/dartz.dart';
class AppFlowyCloudDeepLink {
final _appLinks = AppLinks();
StreamSubscription<Uri?>? _deeplinkSubscription;
final ValueNotifier<DeepLinkResult?> stateNotifier = ValueNotifier(null);
Completer<Either<FlowyError, UserProfilePB>>? _completer;
AppFlowyCloudDeepLink() {
if (Platform.isWindows) {
// register deep link for Windows
registerProtocolHandler(appflowyDeepLinkSchema);
}
_deeplinkSubscription = _appLinks.uriLinkStream.listen(
(Uri? uri) async {
Log.info('onDeepLink: ${uri.toString()}');
await _handleUri(uri);
},
onError: (Object err, StackTrace stackTrace) {
Log.error('on deeplink stream error: ${err.toString()}', stackTrace);
_deeplinkSubscription?.cancel();
},
);
}
Future<void> dispose() async {
await _deeplinkSubscription?.cancel();
}
void resigerCompleter(
Completer<Either<FlowyError, UserProfilePB>> completer,
) {
_completer = completer;
}
VoidCallback subscribeDeepLinkLoadingState(
ValueChanged<DeepLinkResult> listener,
) {
listenerFn() {
if (stateNotifier.value != null) {
listener(stateNotifier.value!);
}
}
stateNotifier.addListener(listenerFn);
return listenerFn;
}
void unsubscribeDeepLinkLoadingState(
VoidCallback listener,
) {
stateNotifier.removeListener(listener);
}
Future<void> _handleUri(
Uri? uri,
) async {
stateNotifier.value = DeepLinkResult(state: DeepLinkState.none);
if (uri != null) {
if (_isAuthCallbackDeeplink(uri)) {
final deviceId = await getDeviceId();
final payload = OauthSignInPB(
authType: AuthTypePB.AFCloud,
map: {
AuthServiceMapKeys.signInURL: uri.toString(),
AuthServiceMapKeys.deviceId: deviceId,
},
);
stateNotifier.value = DeepLinkResult(state: DeepLinkState.loading);
final result = await UserEventOauthSignIn(payload)
.send()
.then((value) => value.swap());
stateNotifier.value = DeepLinkResult(
state: DeepLinkState.finish,
result: result,
);
// If there is no completer, runAppFlowy() will be called.
if (_completer == null) {
result.fold(
(err) {
Log.error(err);
final context = AppGlobals.rootNavKey.currentState?.context;
if (context != null) {
showSnackBarMessage(
context,
err.msg,
);
}
},
(err) async {
Log.error(err);
await runAppFlowy();
},
);
} else {
_completer?.complete(result);
_completer = null;
}
} else {
Log.error('onDeepLinkError: Unexpect deep link: ${uri.toString()}');
_completer?.complete(left(AuthError.signInWithOauthError));
_completer = null;
}
} else {
Log.error('onDeepLinkError: Unexpect empty deep link callback');
_completer?.complete(left(AuthError.emptyDeeplink));
_completer = null;
}
}
bool _isAuthCallbackDeeplink(Uri uri) {
return (uri.fragment.contains('access_token'));
}
}
class InitAppFlowyCloudTask extends LaunchTask {
UserAuthStateListener? _authStateListener;
@ -29,11 +157,6 @@ class InitAppFlowyCloudTask extends LaunchTask {
}
},
);
if (Platform.isWindows) {
// register deep link for Windows
registerProtocolHandler(appflowyDeepLinkSchema);
}
}
@override
@ -42,3 +165,16 @@ class InitAppFlowyCloudTask extends LaunchTask {
_authStateListener = null;
}
}
class DeepLinkResult {
final DeepLinkState state;
final Either<FlowyError, UserProfilePB>? result;
DeepLinkResult({required this.state, this.result});
}
enum DeepLinkState {
none,
loading,
finish,
}