mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: windows callback (#4006)
* tests: fix supabase integration test * ci: fix * chore: fix windows callback
This commit is contained in:
parent
29a6eab1ce
commit
b3dd5fb8bd
8
.github/workflows/flutter_ci.yaml
vendored
8
.github/workflows/flutter_ci.yaml
vendored
@ -300,6 +300,14 @@ jobs:
|
||||
- name: Uncompress appflowy_flutter
|
||||
run: tar -xf appflowy_flutter.tar.gz
|
||||
|
||||
# - name: Create .env.cloud.test file in flowy-server
|
||||
# working-directory: frontend/appflowy_flutter
|
||||
# run: |
|
||||
# touch .env.cloud.test
|
||||
# echo SUPABASE_URL=${{ secrets.SUPABASE_URL }} >> .env.cloud.test
|
||||
# echo SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }} >> .env.cloud.test
|
||||
# echo APPFLOWY_CLOUD_URL=${{ secrets.APPFLOWY_CLOUD_URL }} >> .env.cloud.test
|
||||
|
||||
- name: Run flutter pub get
|
||||
working-directory: frontend
|
||||
run: cargo make pub_get
|
||||
|
20
frontend/appflowy_flutter/env.cloud.test
Normal file
20
frontend/appflowy_flutter/env.cloud.test
Normal file
@ -0,0 +1,20 @@
|
||||
# Only used for integration test
|
||||
|
||||
# Cloud Type Configuration
|
||||
# Use this configuration file to specify the cloud type and its associated settings. The available cloud types are:
|
||||
# Local: 0
|
||||
# Supabase: 1
|
||||
# AppFlowy Cloud: 2
|
||||
# By default, it's set to Local.
|
||||
CLOUD_TYPE=1
|
||||
|
||||
# Supabase Configuration
|
||||
# If using Supabase (CLOUD_TYPE=1), provide the following details:
|
||||
SUPABASE_URL=
|
||||
SUPABASE_ANON_KEY=
|
||||
|
||||
# AppFlowy Cloud Configuration
|
||||
# If using Supabase (CLOUD_TYPE=2), provide the following details:
|
||||
#
|
||||
# When using localhost for development. you can use the following settings:
|
||||
APPFLOWY_CLOUD_URL=
|
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
|
||||
@ -11,13 +12,13 @@ void main() {
|
||||
|
||||
group('auth', () {
|
||||
testWidgets('sign in with supabase', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.initializeAppFlowy(cloudType: CloudType.supabase);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
tester.expectToSeeHomePage();
|
||||
});
|
||||
|
||||
testWidgets('sign out with supabase', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.initializeAppFlowy(cloudType: CloudType.supabase);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
|
||||
// Open the setting page and sign out
|
||||
@ -34,36 +35,37 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('sign in as annoymous', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.initializeAppFlowy(cloudType: CloudType.supabase);
|
||||
await tester.tapSignInAsGuest();
|
||||
|
||||
// should not see the sync setting page when sign in as annoymous
|
||||
await tester.openSettings();
|
||||
await tester.expectNoSettingsPage(SettingsPage.cloud);
|
||||
await tester.openSettingsPage(SettingsPage.user);
|
||||
tester.expectToSeeGoogleLoginButton();
|
||||
});
|
||||
|
||||
testWidgets('enable encryption', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoogleLoginInButton();
|
||||
// testWidgets('enable encryption', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: CloudType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
|
||||
// Open the setting page and sign out
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.cloud);
|
||||
// // Open the setting page and sign out
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.cloud);
|
||||
|
||||
// the switch should be off by default
|
||||
tester.assertEnableEncryptSwitchValue(false);
|
||||
await tester.toggleEnableEncrypt();
|
||||
// // the switch should be off by default
|
||||
// tester.assertEnableEncryptSwitchValue(false);
|
||||
// await tester.toggleEnableEncrypt();
|
||||
|
||||
// the switch should be on after toggling
|
||||
tester.assertEnableEncryptSwitchValue(true);
|
||||
// // the switch should be on after toggling
|
||||
// tester.assertEnableEncryptSwitchValue(true);
|
||||
|
||||
// the switch can not be toggled back to off
|
||||
await tester.toggleEnableEncrypt();
|
||||
tester.assertEnableEncryptSwitchValue(true);
|
||||
});
|
||||
// // the switch can not be toggled back to off
|
||||
// await tester.toggleEnableEncrypt();
|
||||
// tester.assertEnableEncryptSwitchValue(true);
|
||||
// });
|
||||
|
||||
testWidgets('enable sync', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.initializeAppFlowy(cloudType: CloudType.supabase);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
|
||||
// Open the setting page and sign out
|
@ -22,6 +22,7 @@ import 'share_markdown_test.dart' as share_markdown_test;
|
||||
import 'sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
||||
import 'switch_folder_test.dart' as switch_folder_test;
|
||||
import 'tabs_test.dart' as tabs_test;
|
||||
// import 'auth/supabase_auth_test.dart' as supabase_auth_test_runner;
|
||||
|
||||
/// The main task runner for all integration tests in AppFlowy.
|
||||
///
|
||||
@ -74,10 +75,7 @@ Future<void> main() async {
|
||||
// User settings
|
||||
settings_test_runner.main();
|
||||
|
||||
// final cloudType = await getCloudType();
|
||||
// if (cloudType == CloudType.supabase) {
|
||||
// auth_test_runner.main();
|
||||
// }
|
||||
// supabase_auth_test_runner.main();
|
||||
|
||||
// board_test.main();
|
||||
// empty_document_test.main();
|
||||
|
@ -1,12 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/env/cloud_env_test.dart';
|
||||
import 'package:appflowy/startup/entry_point.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/presentation/presentation.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
@ -29,6 +31,7 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
// use to append after the application data directory
|
||||
String? pathExtension,
|
||||
Size windowsSize = const Size(1600, 1200),
|
||||
CloudType? cloudType,
|
||||
}) async {
|
||||
binding.setSurfaceSize(windowsSize);
|
||||
|
||||
@ -38,13 +41,28 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
);
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await FlowyRunner.run(
|
||||
FlowyApp(),
|
||||
IntegrationMode.integrationTest,
|
||||
didInitGetIt: Future(
|
||||
() async {
|
||||
if (cloudType != null) {
|
||||
switch (cloudType) {
|
||||
case CloudType.local:
|
||||
break;
|
||||
case CloudType.supabase:
|
||||
await useSupabaseCloud();
|
||||
break;
|
||||
case CloudType.appflowyCloud:
|
||||
await useAppFlowyCloud();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await waitUntilSignInPageShow();
|
||||
|
||||
return FlowyTestContext(
|
||||
applicationDataDirectory: directory,
|
||||
);
|
||||
@ -207,3 +225,16 @@ extension AppFlowyFinderTestBase on CommonFinders {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> useSupabaseCloud() async {
|
||||
await setCloudType(CloudType.supabase);
|
||||
await setSupbaseServer(
|
||||
Some(TestEnv.supabaseUrl),
|
||||
Some(TestEnv.supabaseAnonKey),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> useAppFlowyCloud() async {
|
||||
await setCloudType(CloudType.appflowyCloud);
|
||||
await setAppFlowyCloudUrl(Some(TestEnv.afCloudUrl));
|
||||
}
|
||||
|
@ -142,13 +142,26 @@ enum CloudType {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
static fromValue(int value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return CloudType.local;
|
||||
case 1:
|
||||
return CloudType.supabase;
|
||||
case 2:
|
||||
return CloudType.appflowyCloud;
|
||||
default:
|
||||
return CloudType.local;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloudType currentCloudType() {
|
||||
return getIt<AppFlowyCloudSharedEnv>().cloudType;
|
||||
}
|
||||
|
||||
Future<void> setAppFlowyCloudBaseUrl(Option<String> url) async {
|
||||
Future<void> setAppFlowyCloudUrl(Option<String> url) async {
|
||||
await url.fold(
|
||||
() => getIt<KeyValueStorage>().remove(KVKeys.kAppflowyCloudBaseURL),
|
||||
(s) => getIt<KeyValueStorage>().set(KVKeys.kAppflowyCloudBaseURL, s),
|
38
frontend/appflowy_flutter/lib/env/cloud_env_test.dart
vendored
Normal file
38
frontend/appflowy_flutter/lib/env/cloud_env_test.dart
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
// lib/env/env.dart
|
||||
import 'package:envied/envied.dart';
|
||||
|
||||
part 'cloud_env_test.g.dart';
|
||||
|
||||
/// Follow the guide on https://supabase.com/docs/guides/auth/social-login/auth-google to setup the auth provider.
|
||||
///
|
||||
@Envied(path: '.env.cloud.test')
|
||||
abstract class TestEnv {
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'CLOUD_TYPE',
|
||||
defaultValue: '0',
|
||||
)
|
||||
static final int cloudType = _TestEnv.cloudType;
|
||||
|
||||
/// AppFlowy Cloud Configuration
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'APPFLOWY_CLOUD_URL',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final String afCloudUrl = _TestEnv.afCloudUrl;
|
||||
|
||||
// Supabase Configuration:
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'SUPABASE_URL',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final String supabaseUrl = _TestEnv.supabaseUrl;
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'SUPABASE_ANON_KEY',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final String supabaseAnonKey = _TestEnv.supabaseAnonKey;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/user/prelude.dart';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/network_monitor.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
@ -37,6 +38,7 @@ class FlowyRunner {
|
||||
static Future<FlowyRunnerContext> run(
|
||||
EntryPoint f,
|
||||
IntegrationMode mode, {
|
||||
Future? didInitGetIt,
|
||||
LaunchConfiguration config = const LaunchConfiguration(
|
||||
autoRegistrationSupported: false,
|
||||
),
|
||||
@ -47,6 +49,8 @@ class FlowyRunner {
|
||||
// Specify the env
|
||||
await initGetIt(getIt, mode, f, config);
|
||||
|
||||
await didInitGetIt;
|
||||
|
||||
final applicationDataDirectory =
|
||||
await getIt<ApplicationDataStorage>().getPath().then(
|
||||
(value) => Directory(value),
|
||||
|
@ -1,7 +1,11 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/startup/tasks/supabase_task.dart';
|
||||
import 'package:appflowy/user/application/user_auth_listener.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:url_protocol/url_protocol.dart';
|
||||
|
||||
class InitAppFlowyCloudTask extends LaunchTask {
|
||||
UserAuthStateListener? _authStateListener;
|
||||
@ -25,6 +29,11 @@ class InitAppFlowyCloudTask extends LaunchTask {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (Platform.isWindows) {
|
||||
// register deep link for Windows
|
||||
registerProtocolHandler(appflowyDeepLinkSchema);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2,7 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/backend_env.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/user/application/auth/device_id.dart';
|
||||
import 'package:appflowy_backend/appflowy_backend.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/user/application/supabase_realtime.dart';
|
||||
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
@ -8,7 +8,6 @@ import 'package:appflowy_backend/log.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';
|
||||
import 'package:nanoid/nanoid.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'auth_error.dart';
|
||||
@ -16,6 +15,7 @@ import 'auth_error.dart';
|
||||
/// Only used for testing.
|
||||
class MockAuthService implements AuthService {
|
||||
MockAuthService();
|
||||
static OauthSignInPB? signInPayload;
|
||||
|
||||
SupabaseClient get _client => Supabase.instance.client;
|
||||
GoTrueClient get _auth => _client.auth;
|
||||
@ -47,15 +47,25 @@ class MockAuthService implements AuthService {
|
||||
required String platform,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
const password = "AppFlowyTest123!";
|
||||
const email = "supabase_integration_test@appflowy.io";
|
||||
try {
|
||||
final response = await _auth.signUp(
|
||||
email: "${nanoid(10)}@appflowy.io",
|
||||
password: "AppFlowyTest123!",
|
||||
);
|
||||
|
||||
final uuid = response.user!.id;
|
||||
final email = response.user!.email!;
|
||||
if (_auth.currentSession == null) {
|
||||
try {
|
||||
await _auth.signInWithPassword(
|
||||
password: password,
|
||||
email: email,
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return Left(AuthError.supabaseSignUpError);
|
||||
}
|
||||
}
|
||||
// Check if the user is already logged in.
|
||||
final session = _auth.currentSession!;
|
||||
final uuid = session.user.id;
|
||||
|
||||
// Create the OAuth sign-in payload.
|
||||
final payload = OauthSignInPB(
|
||||
authType: AuthTypePB.Supabase,
|
||||
map: {
|
||||
@ -65,6 +75,7 @@ class MockAuthService implements AuthService {
|
||||
},
|
||||
);
|
||||
|
||||
// Send the sign-in event and handle the response.
|
||||
return UserEventOauthSignIn(payload).send().then((value) => value.swap());
|
||||
} on AuthException catch (e) {
|
||||
Log.error(e);
|
||||
@ -74,7 +85,7 @@ class MockAuthService implements AuthService {
|
||||
|
||||
@override
|
||||
Future<void> signOut() async {
|
||||
await _auth.signOut();
|
||||
// await _auth.signOut();
|
||||
await _appFlowyAuthService.signOut();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/core/frameless_window.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/user/presentation/widgets/widgets.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/env/backend_env.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -27,15 +27,15 @@ class AppFlowyCloudURLsBloc
|
||||
restartApp: true,
|
||||
),
|
||||
);
|
||||
await setAppFlowyCloudBaseUrl(none());
|
||||
await setAppFlowyCloudUrl(none());
|
||||
} else {
|
||||
validateUrl(state.updatedServerUrl).fold(
|
||||
(error) => emit(state.copyWith(urlError: Some(error))),
|
||||
(_) async {
|
||||
if (state.config.base_url != state.updatedServerUrl) {
|
||||
await setAppFlowyCloudBaseUrl(Some(state.updatedServerUrl));
|
||||
add(const AppFlowyCloudURLsEvent.didSaveConfig());
|
||||
await setAppFlowyCloudUrl(Some(state.updatedServerUrl));
|
||||
}
|
||||
add(const AppFlowyCloudURLsEvent.didSaveConfig());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/env/backend_env.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/defines.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/env/backend_env.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/cloud_setting_bloc.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
|
Loading…
Reference in New Issue
Block a user