diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/launch_settings_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/launch_settings_page.dart new file mode 100644 index 0000000000..c20e8c07c8 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/launch_settings_page.dart @@ -0,0 +1,37 @@ +import 'package:appflowy/env/env.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/presentation.dart'; +import 'package:appflowy/mobile/presentation/setting/self_host_setting_group.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MobileLaunchSettingsPage extends StatelessWidget { + const MobileLaunchSettingsPage({ + super.key, + }); + + static const routeName = '/launch_settings'; + + @override + Widget build(BuildContext context) { + context.watch(); + return Scaffold( + appBar: AppBar( + title: Text(LocaleKeys.settings_title.tr()), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const LanguageSettingGroup(), + if (Env.enableCustomCloud) const SelfHostSettingGroup(), + ], + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/logout_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/logout_setting_group.dart deleted file mode 100644 index af200fd878..0000000000 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/logout_setting_group.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/user/application/auth/auth_service.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; - -class LogoutSettingGroup extends StatelessWidget { - const LogoutSettingGroup({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 4), - child: FlowyButton( - margin: const EdgeInsets.symmetric( - vertical: 16.0, - ), - text: FlowyText.medium( - LocaleKeys.settings_menu_logout.tr(), - textAlign: TextAlign.center, - fontSize: 14.0, - ), - onTap: () async { - await getIt().signOut(); - runAppFlowy(); - }, - ), - ), - ], - ); - } -} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host/self_host_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host/self_host_bottom_sheet.dart new file mode 100644 index 0000000000..04f8fb0471 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host/self_host_bottom_sheet.dart @@ -0,0 +1,110 @@ +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/settings/appflowy_cloud_urls_bloc.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:dartz/dartz.dart' show Some; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class SelfHostUrlBottomSheet extends StatefulWidget { + const SelfHostUrlBottomSheet({ + super.key, + required this.url, + }); + + final String url; + + @override + State createState() => _SelfHostUrlBottomSheetState(); +} + +class _SelfHostUrlBottomSheetState extends State { + final TextEditingController _textFieldController = TextEditingController(); + + final _formKey = GlobalKey(); + @override + void initState() { + super.initState(); + + _textFieldController.text = widget.url; + } + + @override + void dispose() { + _textFieldController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + LocaleKeys.editor_urlHint.tr(), + style: theme.textTheme.labelSmall, + ), + IconButton( + icon: Icon( + Icons.close, + color: theme.hintColor, + ), + onPressed: () { + context.pop(); + }, + ), + ], + ), + const SizedBox( + height: 16, + ), + Form( + key: _formKey, + child: TextFormField( + controller: _textFieldController, + keyboardType: TextInputType.text, + validator: (value) { + if (value == null || value.isEmpty) { + return LocaleKeys.settings_mobile_usernameEmptyError.tr(); + } + return null; + }, + onEditingComplete: _saveSelfHostUrl, + ), + ), + const SizedBox( + height: 16, + ), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _saveSelfHostUrl, + child: Text(LocaleKeys.settings_menu_restartApp.tr()), + ), + ), + ], + ); + } + + void _saveSelfHostUrl() { + if (_formKey.currentState!.validate()) { + final value = _textFieldController.text; + if (value.isNotEmpty) { + validateUrl(value).fold( + (url) async { + await setAppFlowyCloudUrl(Some(url)); + runAppFlowy(); + }, + (err) => Log.error(err), + ); + } + } + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host_setting_group.dart new file mode 100644 index 0000000000..060dacedf0 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host_setting_group.dart @@ -0,0 +1,52 @@ +import 'package:appflowy/env/cloud_env.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; +import 'package:appflowy/mobile/presentation/setting/self_host/self_host_bottom_sheet.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +import 'setting.dart'; + +class SelfHostSettingGroup extends StatefulWidget { + const SelfHostSettingGroup({ + super.key, + }); + + @override + State createState() => _SelfHostSettingGroupState(); +} + +class _SelfHostSettingGroupState extends State { + final future = getAppFlowyCloudUrl(); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: future, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox.shrink(); + } + final url = snapshot.data ?? ''; + return MobileSettingGroup( + groupTitle: LocaleKeys.settings_menu_cloudAppFlowySelfHost.tr(), + settingItemList: [ + MobileSettingItem( + name: url, + onTap: () { + showMobileBottomSheet( + context, + builder: (_) { + return SelfHostUrlBottomSheet( + url: url, + ); + }, + ); + }, + ), + ], + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart index ef8fefa53a..38b9b2d7d7 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart @@ -82,7 +82,6 @@ class InitAppWidgetTask extends LaunchTask { path: 'assets/translations', fallbackLocale: const Locale('en'), useFallbackTranslations: true, - saveLocale: false, child: app, ), ); diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart index 4f98cb5226..87ffc1b005 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart @@ -12,6 +12,7 @@ import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/mobile/presentation/setting/cloud/appflowy_cloud_page.dart'; import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart'; import 'package:appflowy/mobile/presentation/setting/language/language_picker_screen.dart'; +import 'package:appflowy/mobile/presentation/setting/launch_settings_page.dart'; import 'package:appflowy/plugins/base/color/color_picker_screen.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart'; @@ -49,6 +50,7 @@ GoRouter generateRouter(Widget child) { _mobileHomeSettingPageRoute(), _mobileSettingUserAgreementPageRoute(), _mobileCloudSettingAppFlowyCloudPageRoute(), + _mobileLaunchSettingsPageRoute(), // view page _mobileEditorScreenRoute(), @@ -215,6 +217,16 @@ GoRoute _mobileSettingUserAgreementPageRoute() { ); } +GoRoute _mobileLaunchSettingsPageRoute() { + return GoRoute( + parentNavigatorKey: AppGlobals.rootNavKey, + path: MobileLaunchSettingsPage.routeName, + pageBuilder: (context, state) { + return const MaterialPage(child: MobileLaunchSettingsPage()); + }, + ); +} + GoRoute _mobileHomeTrashPageRoute() { return GoRoute( parentNavigatorKey: AppGlobals.rootNavKey, diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart index 9c3a4e2777..83166b312b 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart @@ -1,10 +1,12 @@ 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/mobile/presentation/setting/launch_settings_page.dart'; import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; class MobileSignInScreen extends StatelessWidget { const MobileSignInScreen({ @@ -20,65 +22,89 @@ class MobileSignInScreen extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 40), child: Column( children: [ - const Spacer( - flex: 4, - ), - const FlowySvg( - FlowySvgs.flowy_logo_xl, - size: Size.square(64), - blendMode: null, - ), + const Spacer(flex: 4), + _buildLogo(), const VSpace(spacing * 2), - // Welcome to - FlowyText( - LocaleKeys.welcomeTo.tr(), - textAlign: TextAlign.center, - fontSize: 32, - fontWeight: FontWeight.w700, - ), - // AppFlowy - FlowyText( - LocaleKeys.appName.tr(), - textAlign: TextAlign.center, - fontSize: 32, - color: const Color(0xFF00BCF0), - fontWeight: FontWeight.w700, - ), + _buildWelcomeText(), + _buildAppNameText(colorScheme), const VSpace(spacing), - const Spacer( - flex: 2, - ), - + const Spacer(flex: 2), const SignInAnonymousButton(), const VSpace(spacing), - - // if the cloud env is enabled, show the third-party sign in buttons. if (isAuthEnabled) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Expanded(child: Divider()), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: FlowyText( - LocaleKeys.signIn_or.tr(), - color: colorScheme.onSecondary, - ), - ), - const Expanded(child: Divider()), - ], - ), + _buildThirdPartySignInButtons(colorScheme), const VSpace(spacing), - const ThirdPartySignInButtons(), + _buildSettingsButton(context), ], - if (!isAuthEnabled) - const Spacer( - flex: 2, - ), - const VSpace(spacing), + if (!isAuthEnabled) const Spacer(flex: 2), ], ), ), ); } + + Widget _buildWelcomeText() { + return FlowyText( + LocaleKeys.welcomeTo.tr(), + textAlign: TextAlign.center, + fontSize: 32, + fontWeight: FontWeight.w700, + ); + } + + Widget _buildLogo() { + return const FlowySvg( + FlowySvgs.flowy_logo_xl, + size: Size.square(64), + blendMode: null, + ); + } + + Widget _buildAppNameText(ColorScheme colorScheme) { + return FlowyText( + LocaleKeys.appName.tr(), + textAlign: TextAlign.center, + fontSize: 32, + color: const Color(0xFF00BCF0), + fontWeight: FontWeight.w700, + ); + } + + Widget _buildThirdPartySignInButtons(ColorScheme colorScheme) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Expanded(child: Divider()), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: FlowyText( + LocaleKeys.signIn_or.tr(), + color: colorScheme.onSecondary, + ), + ), + const Expanded(child: Divider()), + ], + ), + const VSpace(16), + const ThirdPartySignInButtons(), + ], + ); + } + + Widget _buildSettingsButton(BuildContext context) { + return FlowyButton( + text: const FlowyText( + 'settings', + textAlign: TextAlign.center, + fontSize: 12.0, + fontWeight: FontWeight.w500, + decoration: TextDecoration.underline, + ), + onTap: () async { + context.push(MobileLaunchSettingsPage.routeName); + }, + ); + } } diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart index 2c3c7f29a1..e951c2ece4 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart @@ -59,6 +59,7 @@ class SignInAnonymousButton extends StatelessWidget { LocaleKeys.signIn_loginStartWithAnonymous.tr(), fontSize: 14, color: Theme.of(context).colorScheme.onPrimary, + fontWeight: FontWeight.w500, ), ); } diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart index 17c8fc0bb3..e25fcf3a35 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart @@ -51,9 +51,10 @@ class MobileSignInOrLogoutButton extends StatelessWidget { ), const HSpace(8), ], - Text( + FlowyText( labelText, - style: Theme.of(context).textTheme.titleSmall, + fontSize: 14.0, + fontWeight: FontWeight.w500, ), ], ), diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart index ee74da0189..8fe8214a36 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart @@ -23,7 +23,7 @@ class ThirdPartySignInButtons extends StatelessWidget { // When user changes themeMode, it changes the state in AppearanceSettingsCubit, but the themeMode for the MaterialApp won't change, it only got updated(get value from AppearanceSettingsCubit) when user open the app again. Thus, we should get themeMode from AppearanceSettingsCubit rather than MediaQuery. final themeModeFromCubit = - context.read().state.themeMode; + context.watch().state.themeMode; final isDarkMode = themeModeFromCubit == ThemeMode.system ? MediaQuery.of(context).platformBrightness == Brightness.dark diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 95f17abd00..127261de3d 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -440,8 +440,8 @@ }, "mobile": { "personalInfo": "Personal Information", - "username": "Username", - "usernameEmptyError": "Username cannot be empty", + "username": "User Name", + "usernameEmptyError": "User name cannot be empty", "about": "About", "pushNotifications": "Push Notifications", "support": "Support",