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 'settings_billing_test.dart' as settings_billing_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() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@ -10,4 +11,5 @@ void main() {
notifications_settings_test.main();
settings_billing_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() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
final BuildContext? highlightContext =
buttonItemKeys[currentHighlight!].currentContext;
if (highlightContext != null) {
Scrollable.ensureVisible(highlightContext);
}
},
debugLabel: 'DropdownMenu.scrollToHighlight',
);
// WidgetsBinding.instance.addPostFrameCallback(
// (_) {
// // try {
// final BuildContext? highlightContext =
// buttonItemKeys[currentHighlight!].currentContext;
// if (highlightContext != null) {
// Scrollable.ensureVisible(highlightContext);
// }
// } catch (_) {
// return;
// }
// },
// debugLabel: 'DropdownMenu.scrollToHighlight',
// );
}
double? getWidth(GlobalKey key) {

View File

@ -68,7 +68,7 @@ class DesktopSignInScreen extends StatelessWidget {
const Row(
mainAxisSize: MainAxisSize.min,
children: [
_SettingsButton(),
DesktopSignInSettingsButton(),
HSpace(42),
SignInAnonymousButtonV2(),
],
@ -92,8 +92,10 @@ class DesktopSignInScreen extends StatelessWidget {
}
}
class _SettingsButton extends StatelessWidget {
const _SettingsButton();
class DesktopSignInSettingsButton extends StatelessWidget {
const DesktopSignInSettingsButton({
super.key,
});
@override
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_shortcuts_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_dropdown.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/settings_menu.dart';
@ -30,6 +32,10 @@ import 'package:toastification/toastification.dart';
import 'widgets/setting_cloud.dart';
@visibleForTesting
const kSelfHostedTextInputFieldKey =
ValueKey('self_hosted_url_input_text_field');
class SettingsDialog extends StatelessWidget {
SettingsDialog(
this.user, {
@ -173,31 +179,33 @@ class _SimpleSettingsDialogState extends State<SimpleSettingsDialog> {
return FlowyDialog(
width: MediaQuery.of(context).size.width * 0.7,
constraints: const BoxConstraints(maxWidth: 784, minWidth: 564),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// header
FlowyText(
LocaleKeys.signIn_settings.tr(),
fontSize: 36.0,
fontWeight: FontWeight.w600,
),
const VSpace(18.0),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// header
FlowyText(
LocaleKeys.signIn_settings.tr(),
fontSize: 36.0,
fontWeight: FontWeight.w600,
),
const VSpace(18.0),
// language
_LanguageSettings(key: ValueKey('language${settings.hashCode}')),
const VSpace(22.0),
// language
_LanguageSettings(key: ValueKey('language${settings.hashCode}')),
const VSpace(22.0),
// self-host cloud
_SelfHostSettings(key: ValueKey('selfhost${settings.hashCode}')),
const VSpace(22.0),
// self-host cloud
_SelfHostSettings(key: ValueKey('selfhost${settings.hashCode}')),
const VSpace(22.0),
// support
_SupportSettings(key: ValueKey('support${settings.hashCode}')),
],
// support
_SupportSettings(key: ValueKey('support${settings.hashCode}')),
],
),
),
),
);
@ -229,6 +237,7 @@ class _SelfHostSettings extends StatefulWidget {
class _SelfHostSettingsState extends State<_SelfHostSettings> {
final textController = TextEditingController();
AuthenticatorType type = AuthenticatorType.appflowyCloud;
@override
void initState() {
@ -236,6 +245,11 @@ class _SelfHostSettingsState extends State<_SelfHostSettings> {
getAppFlowyCloudUrl().then((url) {
textController.text = url;
if (kAppflowyCloudUrl != url) {
setState(() {
type = AuthenticatorType.appflowyCloudSelfHost;
});
}
});
}
@ -248,42 +262,81 @@ class _SelfHostSettingsState extends State<_SelfHostSettings> {
@override
Widget build(BuildContext context) {
return SettingsCategory(
title: LocaleKeys.settings_menu_cloudAppFlowySelfHost.tr(),
title: LocaleKeys.settings_menu_cloudAppFlowy.tr(),
children: [
Row(
children: [
Expanded(
child: SizedBox(
height: 36,
child: FlowyTextField(
controller: textController,
autoFocus: false,
textStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
),
hintText: 'https://beta.appflowy.cloud',
onEditingComplete: _saveSelfHostUrl,
),
Flexible(
child: SettingsServerDropdownMenu(
selectedServer: type,
onSelected: _onSelected,
),
),
if (type == AuthenticatorType.appflowyCloudSelfHost) _buildInputField(),
],
);
}
Widget _buildInputField() {
return Row(
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,
constraints: const BoxConstraints(minWidth: 78),
child: OutlinedRoundedButton(
text: LocaleKeys.button_save.tr(),
onTap: _saveSelfHostUrl,
),
),
),
const HSpace(12.0),
Container(
height: 36,
constraints: const BoxConstraints(minWidth: 78),
child: OutlinedRoundedButton(
text: LocaleKeys.button_save.tr(),
onTap: () => _saveUrl(
url: textController.text,
type: AuthenticatorType.appflowyCloudSelfHost,
),
],
),
),
],
);
}
void _saveSelfHostUrl() {
final url = textController.text;
void _onSelected(AuthenticatorType type) {
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) {
showToastNotification(
context,
@ -301,7 +354,7 @@ class _SelfHostSettingsState extends State<_SelfHostSettings> {
);
Navigator.of(context).pop();
await useSelfHostedAppFlowyCloudWithURL(url);
await useAppFlowyBetaCloudWithURL(url, type);
await runAppFlowy();
},
(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 {
const _SupportSettings({
super.key,

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/flutter/af_dropdown_menu.dart';
import 'package:appflowy/shared/google_fonts_extension.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:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SettingsDropdown<T> extends StatefulWidget {