test: add supabase auth tests (#3250)

* test: add supabase auth tests

* chore: format before test

* chore: fix warnings

* ci: rust test

* chore: disable clicking get started button when logining through the google OAuth
This commit is contained in:
Nathan.fooo 2023-08-22 15:40:22 +08:00 committed by GitHub
parent 12d6cbd46a
commit 77637ff461
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 457 additions and 168 deletions

View File

@ -0,0 +1,85 @@
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';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('auth', () {
testWidgets('sign in with supabase', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoogleLoginInButton();
tester.expectToSeeHomePage();
});
testWidgets('sign out with supabase', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoogleLoginInButton();
// Open the setting page and sign out
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.user);
await tester.tapButton(find.byType(SettingLogoutButton));
tester.expectToSeeText(LocaleKeys.button_OK.tr());
await tester.tapButtonWithName(LocaleKeys.button_OK.tr());
// Go to the sign in page again
await tester.pumpAndSettle(const Duration(seconds: 1));
tester.expectToSeeGoogleLoginButton();
});
testWidgets('sign in as annoymous', (tester) async {
await tester.initializeAppFlowy();
await tester.tapSignInAsGuest();
// should not see the sync setting page when sign in as annoymous
await tester.openSettings();
await tester.expectNoSettingsPage(SettingsPage.syncSetting);
});
testWidgets('enable encryption', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoogleLoginInButton();
// Open the setting page and sign out
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.syncSetting);
// 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 can not be toggled back to off
await tester.toggleEnableEncrypt();
tester.assertEnableEncryptSwitchValue(true);
});
testWidgets('enable sync', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoogleLoginInButton();
// Open the setting page and sign out
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.syncSetting);
// the switch should be on by default
tester.assertEnableSyncSwitchValue(true);
await tester.toggleEnableSync();
// the switch should be off
tester.assertEnableSyncSwitchValue(false);
// the switch should be on after toggling
await tester.toggleEnableSync();
tester.assertEnableSyncSwitchValue(true);
});
});
}

View File

@ -1,3 +1,4 @@
import 'package:appflowy/env/env.dart';
import 'package:integration_test/integration_test.dart';
import 'database_calendar_test.dart' as database_calendar_test;
@ -19,6 +20,7 @@ import 'board/board_test_runner.dart' as board_test_runner;
import 'tabs_test.dart' as tabs_test;
import 'hotkeys_test.dart' as hotkeys_test;
import 'appearance_settings_test.dart' as appearance_test_runner;
import 'auth/auth_test.dart' as auth_test_runner;
/// The main task runner for all integration tests in AppFlowy.
///
@ -63,6 +65,10 @@ void main() {
// Appearance integration test
appearance_test_runner.main();
if (isSupabaseEnabled) {
auth_test_runner.main();
}
// board_test.main();
// empty_document_test.main();
// smart_menu_test.main();

View File

@ -0,0 +1,64 @@
import 'package:appflowy/user/presentation/sign_in_screen.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/sync_setting_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'base.dart';
extension AppFlowyAuthTest on WidgetTester {
Future<void> tapGoogleLoginInButton() async {
await tapButton(find.byType(GoogleSignUpButton));
}
Future<void> tapSignInAsGuest() async {
await tapButton(find.byType(SignInAsGuestButton));
}
void expectToSeeGoogleLoginButton() {
expect(find.byType(GoogleSignUpButton), findsOneWidget);
}
void assertSwitchValue(Finder finder, bool value) {
final Switch switchWidget = widget(finder);
final isSwitched = switchWidget.value;
assert(isSwitched == value);
}
void assertEnableEncryptSwitchValue(bool value) {
assertSwitchValue(
find.descendant(
of: find.byType(EnableEncrypt),
matching: find.byWidgetPredicate((widget) => widget is Switch),
),
value,
);
}
void assertEnableSyncSwitchValue(bool value) {
assertSwitchValue(
find.descendant(
of: find.byType(EnableSync),
matching: find.byWidgetPredicate((widget) => widget is Switch),
),
value,
);
}
Future<void> toggleEnableEncrypt() async {
final finder = find.descendant(
of: find.byType(EnableEncrypt),
matching: find.byWidgetPredicate((widget) => widget is Switch),
);
await tapButton(finder);
}
Future<void> toggleEnableSync() async {
final finder = find.descendant(
of: find.byType(EnableSync),
matching: find.byWidgetPredicate((widget) => widget is Switch),
);
await tapButton(finder);
}
}

View File

@ -29,6 +29,14 @@ extension AppFlowySettings on WidgetTester {
return;
}
Future<void> expectNoSettingsPage(SettingsPage page) async {
final button = find.byWidgetPredicate(
(widget) => widget is SettingsMenuElement && widget.page == page,
);
expect(button, findsNothing);
return;
}
/// Restore the AppFlowy data storage location
Future<void> restoreLocation() async {
final button =

View File

@ -6,3 +6,4 @@ export 'expectation.dart';
export 'editor_test_operations.dart';
export 'mock/mock_url_launcher.dart';
export 'ime.dart';
export 'auth_operation.dart';

View File

@ -40,7 +40,7 @@ abstract class Env {
bool get isSupabaseEnabled {
// Only enable supabase in release and develop mode.
if (integrationEnv().isRelease || integrationEnv().isDevelop) {
if (integrationMode().isRelease || integrationMode().isDevelop) {
return Env.supabaseUrl.isNotEmpty &&
Env.supabaseAnonKey.isNotEmpty &&
Env.supabaseJwtSecret.isNotEmpty;

View File

@ -12,6 +12,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/ser
import 'package:appflowy/plugins/trash/application/prelude.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/auth/mock_auth_service.dart';
import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
import 'package:appflowy/user/application/prelude.dart';
import 'package:appflowy/user/application/user_listener.dart';
@ -38,7 +39,7 @@ class DependencyResolver {
GetIt getIt,
IntegrationMode mode,
) async {
_resolveUserDeps(getIt);
_resolveUserDeps(getIt, mode);
_resolveHomeDeps(getIt);
_resolveFolderDeps(getIt);
_resolveDocDeps(getIt);
@ -86,9 +87,13 @@ void _resolveCommonService(
);
}
void _resolveUserDeps(GetIt getIt) {
void _resolveUserDeps(GetIt getIt, IntegrationMode mode) {
if (isSupabaseEnabled) {
getIt.registerFactory<AuthService>(() => SupabaseAuthService());
if (mode.isIntegrationTest) {
getIt.registerFactory<AuthService>(() => MockAuthService());
} else {
getIt.registerFactory<AuthService>(() => SupabaseAuthService());
}
} else {
getIt.registerFactory<AuthService>(() => AppFlowyAuthService());
}

View File

@ -27,7 +27,7 @@ class FlowyRunnerContext {
Future<void> runAppFlowy() async {
await FlowyRunner.run(
FlowyApp(),
integrationEnv(),
integrationMode(),
);
}
@ -86,7 +86,7 @@ class FlowyRunner {
Future<void> initGetIt(
GetIt getIt,
IntegrationMode env,
IntegrationMode mode,
EntryPoint f,
LaunchConfiguration config,
) async {
@ -98,14 +98,14 @@ Future<void> initGetIt(
() => AppLauncher(
context: LaunchContext(
getIt,
env,
mode,
config,
),
),
);
getIt.registerSingleton<PluginSandbox>(PluginSandbox());
await DependencyResolver.resolve(getIt, env);
await DependencyResolver.resolve(getIt, mode);
}
class LaunchContext {
@ -171,7 +171,7 @@ enum IntegrationMode {
bool get isDevelop => this == IntegrationMode.develop;
}
IntegrationMode integrationEnv() {
IntegrationMode integrationMode() {
if (Platform.environment.containsKey('FLUTTER_TEST')) {
return IntegrationMode.unitTest;
}

View File

@ -45,7 +45,7 @@ AppFlowyEnv getAppFlowyEnv() {
/// The default directory to store the user data. The directory can be
/// customized by the user via the [ApplicationDataStorage]
Future<Directory> appFlowyApplicationDataDirectory() async {
switch (integrationEnv()) {
switch (integrationMode()) {
case IntegrationMode.develop:
final Directory documentsDir = await getApplicationSupportDirectory()
..create();

View File

@ -19,7 +19,7 @@ class AppFlowyAuthService implements AuthService {
required String email,
required String password,
AuthTypePB authType = AuthTypePB.Local,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) async {
final request = SignInPayloadPB.create()
..email = email
@ -36,7 +36,7 @@ class AppFlowyAuthService implements AuthService {
required String email,
required String password,
AuthTypePB authType = AuthTypePB.Local,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) async {
final request = SignUpPayloadPB.create()
..name = name
@ -53,7 +53,7 @@ class AppFlowyAuthService implements AuthService {
@override
Future<void> signOut({
AuthTypePB authType = AuthTypePB.Local,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) async {
await UserEventSignOut().send();
return;
@ -62,7 +62,7 @@ class AppFlowyAuthService implements AuthService {
@override
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
AuthTypePB authType = AuthTypePB.Local,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) {
const password = "Guest!@123456";
final uid = uuid();
@ -78,7 +78,7 @@ class AppFlowyAuthService implements AuthService {
Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
required String platform,
AuthTypePB authType = AuthTypePB.Local,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) async {
return left(
FlowyError.create()
@ -95,7 +95,7 @@ class AppFlowyAuthService implements AuthService {
@override
Future<Either<FlowyError, UserProfilePB>> signInWithMagicLink({
required String email,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) async {
return left(
FlowyError.create()

View File

@ -18,7 +18,7 @@ abstract class AuthService {
required String email,
required String password,
AuthTypePB authType,
Map<String, String> map,
Map<String, String> params,
});
/// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError].
@ -27,25 +27,25 @@ abstract class AuthService {
required String email,
required String password,
AuthTypePB authType,
Map<String, String> map,
Map<String, String> params,
});
///
Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
required String platform,
AuthTypePB authType,
Map<String, String> map,
Map<String, String> params,
});
/// Returns a default [UserProfilePB]
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
AuthTypePB authType,
Map<String, String> map,
Map<String, String> params,
});
Future<Either<FlowyError, UserProfilePB>> signInWithMagicLink({
required String email,
Map<String, String> map,
Map<String, String> params,
});
///

View File

@ -8,7 +8,7 @@ import 'package:flutter/services.dart';
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
Future<String> getDeviceId() async {
if (integrationEnv().isTest) {
if (integrationMode().isTest) {
return "test_device_id";
}

View File

@ -0,0 +1,110 @@
import 'dart:async';
import 'package:appflowy/user/application/auth/appflowy_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
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';
/// Only used for testing.
class MockAuthService implements AuthService {
MockAuthService();
SupabaseClient get _client => Supabase.instance.client;
GoTrueClient get _auth => _client.auth;
final AppFlowyAuthService _appFlowyAuthService = AppFlowyAuthService();
@override
Future<Either<FlowyError, UserProfilePB>> signUp({
required String name,
required String email,
required String password,
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> params = const {},
}) async {
throw UnimplementedError();
}
@override
Future<Either<FlowyError, UserProfilePB>> signIn({
required String email,
required String password,
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> params = const {},
}) async {
throw UnimplementedError();
}
@override
Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
required String platform,
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> params = const {},
}) async {
try {
final response = await _auth.signUp(
email: "${nanoid(10)}@appflowy.io",
password: "AppFlowyTest123!",
);
final uuid = response.user!.id;
final email = response.user!.email!;
final payload = ThirdPartyAuthPB(
authType: AuthTypePB.Supabase,
map: {
AuthServiceMapKeys.uuid: uuid,
AuthServiceMapKeys.email: email,
AuthServiceMapKeys.deviceId: 'MockDeviceId'
},
);
return UserEventThirdPartyAuth(payload)
.send()
.then((value) => value.swap());
} on AuthException catch (e) {
Log.error(e);
return Left(AuthError.supabaseSignInError);
}
}
@override
Future<void> signOut({
AuthTypePB authType = AuthTypePB.Supabase,
}) async {
await _auth.signOut();
await _appFlowyAuthService.signOut(
authType: authType,
);
}
@override
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> params = const {},
}) async {
// supabase don't support guest login.
// so, just forward to our backend.
return _appFlowyAuthService.signUpAsGuest();
}
@override
Future<Either<FlowyError, UserProfilePB>> signInWithMagicLink({
required String email,
Map<String, String> params = const {},
}) async {
throw UnimplementedError();
}
@override
Future<Either<FlowyError, UserProfilePB>> getUser() async {
return UserBackendService.getCurrentUserProfile();
}
}

View File

@ -1,6 +1,5 @@
import 'dart:async';
import 'package:appflowy/env/env.dart';
import 'package:appflowy/startup/tasks/prelude.dart';
import 'package:appflowy/user/application/auth/appflowy_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
@ -29,16 +28,8 @@ class SupabaseAuthService implements AuthService {
required String email,
required String password,
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) async {
if (!isSupabaseEnabled) {
return _appFlowyAuthService.signUp(
name: name,
email: email,
password: password,
);
}
// fetch the uuid from supabase.
final response = await _auth.signUp(
email: email,
@ -55,7 +46,7 @@ class SupabaseAuthService implements AuthService {
email: email,
password: password,
authType: authType,
map: {
params: {
AuthServiceMapKeys.uuid: uuid,
},
);
@ -66,15 +57,8 @@ class SupabaseAuthService implements AuthService {
required String email,
required String password,
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) async {
if (!isSupabaseEnabled) {
return _appFlowyAuthService.signIn(
email: email,
password: password,
);
}
try {
final response = await _auth.signInWithPassword(
email: email,
@ -88,7 +72,7 @@ class SupabaseAuthService implements AuthService {
email: email,
password: password,
authType: authType,
map: {
params: {
AuthServiceMapKeys.uuid: uuid,
},
);
@ -102,11 +86,8 @@ class SupabaseAuthService implements AuthService {
Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
required String platform,
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) async {
if (!isSupabaseEnabled) {
return _appFlowyAuthService.signUpWithOAuth(platform: platform);
}
// Before signing in, sign out any existing users. Otherwise, the callback will be triggered even if the user doesn't click the 'Sign In' button on the website
if (_auth.currentUser != null) {
await _auth.signOut();
@ -115,7 +96,7 @@ class SupabaseAuthService implements AuthService {
final provider = platform.toProvider();
final completer = supabaseLoginCompleter(
onSuccess: (userId, userEmail) async {
return await setupAuth(
return await _setupAuth(
map: {
AuthServiceMapKeys.uuid: userId,
AuthServiceMapKeys.email: userEmail,
@ -140,9 +121,7 @@ class SupabaseAuthService implements AuthService {
Future<void> signOut({
AuthTypePB authType = AuthTypePB.Supabase,
}) async {
if (isSupabaseEnabled) {
await _auth.signOut();
}
await _auth.signOut();
await _appFlowyAuthService.signOut(
authType: authType,
);
@ -151,7 +130,7 @@ class SupabaseAuthService implements AuthService {
@override
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) async {
// supabase don't support guest login.
// so, just forward to our backend.
@ -161,11 +140,11 @@ class SupabaseAuthService implements AuthService {
@override
Future<Either<FlowyError, UserProfilePB>> signInWithMagicLink({
required String email,
Map<String, String> map = const {},
Map<String, String> params = const {},
}) async {
final completer = supabaseLoginCompleter(
onSuccess: (userId, userEmail) async {
return await setupAuth(
return await _setupAuth(
map: {
AuthServiceMapKeys.uuid: userId,
AuthServiceMapKeys.email: userEmail,
@ -195,7 +174,7 @@ class SupabaseAuthService implements AuthService {
return Right(user);
}
Future<Either<FlowyError, UserProfilePB>> setupAuth({
Future<Either<FlowyError, UserProfilePB>> _setupAuth({
required Map<String, String> map,
}) async {
final payload = ThirdPartyAuthPB(

View File

@ -222,46 +222,59 @@ class SignInAsGuestButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => HistoricalUserBloc()
..add(
const HistoricalUserEvent.initial(),
),
child: BlocListener<HistoricalUserBloc, HistoricalUserState>(
listenWhen: (previous, current) =>
previous.openedHistoricalUser != current.openedHistoricalUser,
listener: (context, state) async {
await runAppFlowy();
},
child: BlocBuilder<HistoricalUserBloc, HistoricalUserState>(
builder: (context, state) {
if (state.historicalUsers.isEmpty) {
return RoundedTextButton(
title: LocaleKeys.signIn_loginAsGuestButtonText.tr(),
height: 48,
borderRadius: Corners.s6Border,
onPressed: () {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'local');
context
.read<SignInBloc>()
.add(const SignInEvent.signedInAsGuest());
},
);
} else {
return RoundedTextButton(
title: LocaleKeys.signIn_continueAnonymousUser.tr(),
height: 48,
borderRadius: Corners.s6Border,
onPressed: () {
final bloc = context.read<HistoricalUserBloc>();
final user = bloc.state.historicalUsers.first;
bloc.add(HistoricalUserEvent.openHistoricalUser(user));
},
);
}
},
),
),
return BlocBuilder<SignInBloc, SignInState>(
builder: (context, signInState) {
return BlocProvider(
create: (context) => HistoricalUserBloc()
..add(
const HistoricalUserEvent.initial(),
),
child: BlocListener<HistoricalUserBloc, HistoricalUserState>(
listenWhen: (previous, current) =>
previous.openedHistoricalUser != current.openedHistoricalUser,
listener: (context, state) async {
await runAppFlowy();
},
child: BlocBuilder<HistoricalUserBloc, HistoricalUserState>(
builder: (context, state) {
final text = state.historicalUsers.isEmpty
? FlowyText.medium(
LocaleKeys.signIn_loginAsGuestButtonText.tr(),
textAlign: TextAlign.center,
)
: FlowyText.medium(
LocaleKeys.signIn_continueAnonymousUser.tr(),
textAlign: TextAlign.center,
);
final onTap = state.historicalUsers.isEmpty
? () {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'local');
context
.read<SignInBloc>()
.add(const SignInEvent.signedInAsGuest());
}
: () {
final bloc = context.read<HistoricalUserBloc>();
final user = bloc.state.historicalUsers.first;
bloc.add(HistoricalUserEvent.openHistoricalUser(user));
};
return SizedBox(
height: 48,
child: FlowyButton(
isSelected: true,
disable: signInState.isSubmitting,
text: text,
radius: Corners.s6Border,
onTap: onTap,
),
);
},
),
),
);
},
);
}
}
@ -410,16 +423,8 @@ class ThirdPartySignInButtons extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: mainAxisAlignment,
children: [
ThirdPartySignInButton(
icon: FlowySvgs.google_mark_xl,
onPressed: () {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
context.read<SignInBloc>().add(
const SignInEvent.signedInWithOAuth('google'),
);
},
),
children: const [
GoogleSignUpButton(),
// const SizedBox(width: 20),
// ThirdPartySignInButton(
// icon: 'login/github-mark',
@ -444,3 +449,20 @@ class ThirdPartySignInButtons extends StatelessWidget {
);
}
}
class GoogleSignUpButton extends StatelessWidget {
const GoogleSignUpButton({super.key});
@override
Widget build(BuildContext context) {
return ThirdPartySignInButton(
icon: FlowySvgs.google_mark_xl,
onPressed: () {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
context.read<SignInBloc>().add(
const SignInEvent.signedInWithOAuth('google'),
);
},
);
}
}

View File

@ -108,7 +108,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
Future<void> _relaunchAppAndAutoRegister() async {
await FlowyRunner.run(
FlowyApp(),
integrationEnv(),
integrationMode(),
config: const LaunchConfiguration(
autoRegistrationSupported: true,
),

View File

@ -177,7 +177,7 @@ class _ChangeStoragePathButtonState extends State<_ChangeStoragePathButton> {
await context.read<SettingsLocationCubit>().setCustomPath(path);
await FlowyRunner.run(
FlowyApp(),
integrationEnv(),
integrationMode(),
config: const LaunchConfiguration(
autoRegistrationSupported: true,
),
@ -252,7 +252,7 @@ class _RecoverDefaultStorageButtonState
.resetDataStoragePathToApplicationDefault();
await FlowyRunner.run(
FlowyApp(),
integrationEnv(),
integrationMode(),
config: const LaunchConfiguration(
autoRegistrationSupported: true,
),

View File

@ -82,8 +82,7 @@ class SettingsUserView extends StatelessWidget {
);
}
}
return _renderLogoutButton(context);
return SettingLogoutButton(user: user, didLogout: didLogout);
}
Widget _renderUserNameInput(BuildContext context) {
@ -106,40 +105,6 @@ class SettingsUserView extends StatelessWidget {
context.read<SettingsUserViewBloc>().state.userProfile.openaiKey;
return _OpenaiKeyInput(openAIKey);
}
Widget _renderLogoutButton(BuildContext context) {
return Center(
child: SizedBox(
width: 160,
child: FlowyButton(
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 2.0),
text: FlowyText.medium(
LocaleKeys.settings_menu_logout.tr(),
fontSize: 13,
textAlign: TextAlign.center,
),
onTap: () async {
NavigatorAlertDialog(
title: logoutPromptMessage(),
confirm: () async {
await getIt<AuthService>().signOut();
didLogout();
},
).show(context);
},
),
),
);
}
String logoutPromptMessage() {
switch (user.encryptionType) {
case EncryptionTypePB.Symmetric:
return LocaleKeys.settings_menu_selfEncryptionLogoutPrompt.tr();
default:
return LocaleKeys.settings_menu_logoutPrompt.tr();
}
}
}
@visibleForTesting
@ -409,3 +374,48 @@ class IconOption extends StatelessWidget {
);
}
}
class SettingLogoutButton extends StatelessWidget {
final UserProfilePB user;
final VoidCallback didLogout;
const SettingLogoutButton({
required this.user,
required this.didLogout,
super.key,
});
@override
Widget build(BuildContext context) {
return Center(
child: SizedBox(
width: 160,
child: FlowyButton(
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 2.0),
text: FlowyText.medium(
LocaleKeys.settings_menu_logout.tr(),
fontSize: 13,
textAlign: TextAlign.center,
),
onTap: () async {
NavigatorAlertDialog(
title: logoutPromptMessage(),
confirm: () async {
await getIt<AuthService>().signOut();
didLogout();
},
).show(context);
},
),
),
);
}
String logoutPromptMessage() {
switch (user.encryptionType) {
case EncryptionTypePB.Symmetric:
return LocaleKeys.settings_menu_selfEncryptionLogoutPrompt.tr();
default:
return LocaleKeys.settings_menu_logoutPrompt.tr();
}
}
}

View File

@ -43,30 +43,26 @@ class FlowyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (!disable) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
onSecondaryTap: onSecondaryTap,
child: FlowyHover(
style: HoverStyle(
borderRadius: radius ?? Corners.s6Border,
hoverColor: hoverColor ?? Theme.of(context).colorScheme.secondary,
),
onHover: onHover,
isSelected: () => isSelected,
builder: (context, onHover) => _render(),
final color = hoverColor ?? Theme.of(context).colorScheme.secondary;
final alpha = (255 * disableOpacity).toInt();
color.withAlpha(alpha);
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: disable ? null : onTap,
onSecondaryTap: disable ? null : onSecondaryTap,
child: FlowyHover(
cursor:
disable ? SystemMouseCursors.forbidden : SystemMouseCursors.click,
style: HoverStyle(
borderRadius: radius ?? Corners.s6Border,
hoverColor: color,
),
);
} else {
return Opacity(
opacity: disableOpacity,
child: MouseRegion(
cursor: SystemMouseCursors.forbidden,
child: _render(),
),
);
}
onHover: disable ? null : onHover,
isSelected: () => isSelected,
builder: (context, onHover) => _render(),
),
);
}
Widget _render() {

View File

@ -1,5 +1,8 @@
[build]
rustflags = ["--cfg", "tokio_unstable"]
#[target.aarch64-apple-darwin]
#BINDGEN_EXTRA_CLANG_ARGS="--target=aarch64-apple-darwin"
[target.x86_64-apple-darwin]
rustflags = ["-C", "target-cpu=native", "-C", "link-arg=-mmacosx-version-min=11.0"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "target-cpu=native", "-C", "link-arg=-mmacosx-version-min=11.0"]