feat: support appflowy cloud in sign-in settings page (#6386)

* feat: support appflowy cloud in sign-in settings page

* test: add cloud server test

* test: wait until the tooltip disappear
This commit is contained in:
Lucas 2024-09-23 20:19:29 +08:00 committed by GitHub
parent 0d2841227a
commit c51b495544
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 281 additions and 65 deletions

View File

@ -3,6 +3,7 @@ import 'package:integration_test/integration_test.dart';
import 'notifications_settings_test.dart' as notifications_settings_test; import 'notifications_settings_test.dart' as notifications_settings_test;
import 'settings_billing_test.dart' as settings_billing_test; import 'settings_billing_test.dart' as settings_billing_test;
import 'shortcuts_settings_test.dart' as shortcuts_settings_test; import 'shortcuts_settings_test.dart' as shortcuts_settings_test;
import 'sign_in_page_settings_test.dart' as sign_in_page_settings_test;
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@ -10,4 +11,5 @@ void main() {
notifications_settings_test.main(); notifications_settings_test.main();
settings_billing_test.main(); settings_billing_test.main();
shortcuts_settings_test.main(); shortcuts_settings_test.main();
sign_in_page_settings_test.main();
} }

View File

@ -0,0 +1,103 @@
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/desktop_sign_in_screen.dart';
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:toastification/toastification.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
Finder findServerType(AuthenticatorType type) {
return find
.descendant(
of: find.byType(SettingsServerDropdownMenu),
matching: find.findTextInFlowyText(
type.label,
),
)
.last;
}
group('sign-in page settings: ', () {
testWidgets('change server type', (tester) async {
await tester.initializeAppFlowy();
// reset the app to the default state
await useAppFlowyBetaCloudWithURL(
kAppflowyCloudUrl,
AuthenticatorType.appflowyCloud,
);
// open the settings page
final settingsButton = find.byType(DesktopSignInSettingsButton);
await tester.tapButton(settingsButton);
expect(find.byType(SimpleSettingsDialog), findsOneWidget);
// the default type should be appflowy cloud
final appflowyCloudType = findServerType(AuthenticatorType.appflowyCloud);
expect(appflowyCloudType, findsOneWidget);
// change the server type to self-host
await tester.tapButton(appflowyCloudType);
final selfhostedButton = findServerType(
AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapButton(selfhostedButton);
// update server url
const serverUrl = 'https://test.appflowy.cloud';
await tester.enterText(
find.byKey(kSelfHostedTextInputFieldKey),
serverUrl,
);
await tester.pumpAndSettle();
await tester.tapButton(
find.findTextInFlowyText(LocaleKeys.button_save.tr()),
);
// wait the app to restart, and the tooltip to disappear
await tester.pumpUntilNotFound(find.byType(BuiltInToastBuilder));
await tester.pumpAndSettle(const Duration(milliseconds: 250));
// open settings page to check the result
await tester.tapButton(settingsButton);
// check the server type
expect(
findServerType(AuthenticatorType.appflowyCloudSelfHost),
findsOneWidget,
);
// check the server url
expect(
find.text(serverUrl),
findsOneWidget,
);
// reset to appflowy cloud
await tester.tapButton(
findServerType(AuthenticatorType.appflowyCloudSelfHost),
);
// change the server type to appflowy cloud
await tester.tapButton(
findServerType(AuthenticatorType.appflowyCloud),
);
// wait the app to restart, and the tooltip to disappear
await tester.pumpUntilNotFound(find.byType(BuiltInToastBuilder));
await tester.pumpAndSettle(const Duration(milliseconds: 250));
// check the server type
await tester.tapButton(settingsButton);
expect(
findServerType(AuthenticatorType.appflowyCloud),
findsOneWidget,
);
});
});
}

View File

@ -391,17 +391,23 @@ class _AFDropdownMenuState<T> extends State<AFDropdownMenu<T>> {
); );
} }
// Remove the code here, it will throw a FlutterError
// Unless we upgrade to Flutter 3.24 https://github.com/flutter/flutter/issues/146764
void scrollToHighlight() { void scrollToHighlight() {
WidgetsBinding.instance.addPostFrameCallback( // WidgetsBinding.instance.addPostFrameCallback(
(_) { // (_) {
final BuildContext? highlightContext = // // try {
buttonItemKeys[currentHighlight!].currentContext; // final BuildContext? highlightContext =
if (highlightContext != null) { // buttonItemKeys[currentHighlight!].currentContext;
Scrollable.ensureVisible(highlightContext); // if (highlightContext != null) {
} // Scrollable.ensureVisible(highlightContext);
}, // }
debugLabel: 'DropdownMenu.scrollToHighlight', // } catch (_) {
); // return;
// }
// },
// debugLabel: 'DropdownMenu.scrollToHighlight',
// );
} }
double? getWidth(GlobalKey key) { double? getWidth(GlobalKey key) {

View File

@ -68,7 +68,7 @@ class DesktopSignInScreen extends StatelessWidget {
const Row( const Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_SettingsButton(), DesktopSignInSettingsButton(),
HSpace(42), HSpace(42),
SignInAnonymousButtonV2(), SignInAnonymousButtonV2(),
], ],
@ -92,8 +92,10 @@ class DesktopSignInScreen extends StatelessWidget {
} }
} }
class _SettingsButton extends StatelessWidget { class DesktopSignInSettingsButton extends StatelessWidget {
const _SettingsButton(); const DesktopSignInSettingsButton({
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -14,7 +14,9 @@ import 'package:appflowy/workspace/presentation/settings/pages/settings_manage_d
import 'package:appflowy/workspace/presentation/settings/pages/settings_plan_view.dart'; import 'package:appflowy/workspace/presentation/settings/pages/settings_plan_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_shortcuts_view.dart'; import 'package:appflowy/workspace/presentation/settings/pages/settings_shortcuts_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_workspace_view.dart'; import 'package:appflowy/workspace/presentation/settings/pages/settings_workspace_view.dart';
import 'package:appflowy/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_dropdown.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/feature_flag_page.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/feature_flag_page.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_page.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_page.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu.dart';
@ -30,6 +32,10 @@ import 'package:toastification/toastification.dart';
import 'widgets/setting_cloud.dart'; import 'widgets/setting_cloud.dart';
@visibleForTesting
const kSelfHostedTextInputFieldKey =
ValueKey('self_hosted_url_input_text_field');
class SettingsDialog extends StatelessWidget { class SettingsDialog extends StatelessWidget {
SettingsDialog( SettingsDialog(
this.user, { this.user, {
@ -173,31 +179,33 @@ class _SimpleSettingsDialogState extends State<SimpleSettingsDialog> {
return FlowyDialog( return FlowyDialog(
width: MediaQuery.of(context).size.width * 0.7, width: MediaQuery.of(context).size.width * 0.7,
constraints: const BoxConstraints(maxWidth: 784, minWidth: 564), constraints: const BoxConstraints(maxWidth: 784, minWidth: 564),
child: Padding( child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0), child: Padding(
child: Column( padding: const EdgeInsets.all(24.0),
mainAxisSize: MainAxisSize.min, child: Column(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min,
children: [ crossAxisAlignment: CrossAxisAlignment.start,
// header children: [
FlowyText( // header
LocaleKeys.signIn_settings.tr(), FlowyText(
fontSize: 36.0, LocaleKeys.signIn_settings.tr(),
fontWeight: FontWeight.w600, fontSize: 36.0,
), fontWeight: FontWeight.w600,
const VSpace(18.0), ),
const VSpace(18.0),
// language // language
_LanguageSettings(key: ValueKey('language${settings.hashCode}')), _LanguageSettings(key: ValueKey('language${settings.hashCode}')),
const VSpace(22.0), const VSpace(22.0),
// self-host cloud // self-host cloud
_SelfHostSettings(key: ValueKey('selfhost${settings.hashCode}')), _SelfHostSettings(key: ValueKey('selfhost${settings.hashCode}')),
const VSpace(22.0), const VSpace(22.0),
// support // support
_SupportSettings(key: ValueKey('support${settings.hashCode}')), _SupportSettings(key: ValueKey('support${settings.hashCode}')),
], ],
),
), ),
), ),
); );
@ -229,6 +237,7 @@ class _SelfHostSettings extends StatefulWidget {
class _SelfHostSettingsState extends State<_SelfHostSettings> { class _SelfHostSettingsState extends State<_SelfHostSettings> {
final textController = TextEditingController(); final textController = TextEditingController();
AuthenticatorType type = AuthenticatorType.appflowyCloud;
@override @override
void initState() { void initState() {
@ -236,6 +245,11 @@ class _SelfHostSettingsState extends State<_SelfHostSettings> {
getAppFlowyCloudUrl().then((url) { getAppFlowyCloudUrl().then((url) {
textController.text = url; textController.text = url;
if (kAppflowyCloudUrl != url) {
setState(() {
type = AuthenticatorType.appflowyCloudSelfHost;
});
}
}); });
} }
@ -248,42 +262,81 @@ class _SelfHostSettingsState extends State<_SelfHostSettings> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SettingsCategory( return SettingsCategory(
title: LocaleKeys.settings_menu_cloudAppFlowySelfHost.tr(), title: LocaleKeys.settings_menu_cloudAppFlowy.tr(),
children: [ children: [
Row( Flexible(
children: [ child: SettingsServerDropdownMenu(
Expanded( selectedServer: type,
child: SizedBox( onSelected: _onSelected,
height: 36, ),
child: FlowyTextField( ),
controller: textController, if (type == AuthenticatorType.appflowyCloudSelfHost) _buildInputField(),
autoFocus: false, ],
textStyle: const TextStyle( );
fontSize: 14, }
fontWeight: FontWeight.w400,
), Widget _buildInputField() {
hintText: 'https://beta.appflowy.cloud', return Row(
onEditingComplete: _saveSelfHostUrl, children: [
), Expanded(
child: SizedBox(
height: 36,
child: FlowyTextField(
key: kSelfHostedTextInputFieldKey,
controller: textController,
autoFocus: false,
textStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
),
hintText: kAppflowyCloudUrl,
onEditingComplete: () => _saveUrl(
url: textController.text,
type: AuthenticatorType.appflowyCloudSelfHost,
), ),
), ),
const HSpace(12.0), ),
Container( ),
height: 36, const HSpace(12.0),
constraints: const BoxConstraints(minWidth: 78), Container(
child: OutlinedRoundedButton( height: 36,
text: LocaleKeys.button_save.tr(), constraints: const BoxConstraints(minWidth: 78),
onTap: _saveSelfHostUrl, child: OutlinedRoundedButton(
), text: LocaleKeys.button_save.tr(),
onTap: () => _saveUrl(
url: textController.text,
type: AuthenticatorType.appflowyCloudSelfHost,
), ),
], ),
), ),
], ],
); );
} }
void _saveSelfHostUrl() { void _onSelected(AuthenticatorType type) {
final url = textController.text; if (type == this.type) {
return;
}
Log.info('Switching server type to $type');
setState(() {
this.type = type;
});
if (type == AuthenticatorType.appflowyCloud) {
textController.text = kAppflowyCloudUrl;
_saveUrl(
url: textController.text,
type: type,
);
}
}
void _saveUrl({
required String url,
required AuthenticatorType type,
}) {
if (url.isEmpty) { if (url.isEmpty) {
showToastNotification( showToastNotification(
context, context,
@ -301,7 +354,7 @@ class _SelfHostSettingsState extends State<_SelfHostSettings> {
); );
Navigator.of(context).pop(); Navigator.of(context).pop();
await useSelfHostedAppFlowyCloudWithURL(url); await useAppFlowyBetaCloudWithURL(url, type);
await runAppFlowy(); await runAppFlowy();
}, },
(err) { (err) {
@ -316,6 +369,57 @@ class _SelfHostSettingsState extends State<_SelfHostSettings> {
} }
} }
@visibleForTesting
extension SettingsServerDropdownMenuExtension on AuthenticatorType {
String get label {
switch (this) {
case AuthenticatorType.appflowyCloud:
return LocaleKeys.settings_menu_cloudAppFlowy.tr();
case AuthenticatorType.appflowyCloudSelfHost:
return LocaleKeys.settings_menu_cloudAppFlowySelfHost.tr();
default:
throw Exception('Unsupported server type: $this');
}
}
}
@visibleForTesting
class SettingsServerDropdownMenu extends StatelessWidget {
const SettingsServerDropdownMenu({
super.key,
required this.selectedServer,
required this.onSelected,
});
final AuthenticatorType selectedServer;
final void Function(AuthenticatorType type) onSelected;
// in the settings page from sign in page, we only support appflowy cloud and self-hosted
static final supportedServers = [
AuthenticatorType.appflowyCloud,
AuthenticatorType.appflowyCloudSelfHost,
];
@override
Widget build(BuildContext context) {
return SettingsDropdown<AuthenticatorType>(
expandWidth: false,
onChanged: onSelected,
selectedOption: selectedServer,
options: supportedServers
.map(
(serverType) => buildDropdownMenuEntry<AuthenticatorType>(
context,
selectedValue: selectedServer,
value: serverType,
label: serverType.label,
),
)
.toList(),
);
}
}
class _SupportSettings extends StatelessWidget { class _SupportSettings extends StatelessWidget {
const _SupportSettings({ const _SupportSettings({
super.key, super.key,

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/flutter/af_dropdown_menu.dart'; import 'package:appflowy/flutter/af_dropdown_menu.dart';
import 'package:appflowy/shared/google_fonts_extension.dart'; import 'package:appflowy/shared/google_fonts_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
@ -7,6 +5,7 @@ import 'package:appflowy/workspace/application/settings/appearance/base_appearan
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class SettingsDropdown<T> extends StatefulWidget { class SettingsDropdown<T> extends StatefulWidget {