feat: customize self host url on launch page (#4465)

This commit is contained in:
Lucas.Xu
2024-01-23 16:38:15 +08:00
committed by GitHub
parent 2554ba81b5
commit e239ba46aa
11 changed files with 293 additions and 93 deletions

View File

@ -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<AppearanceSettingsCubit>();
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(),
],
),
),
),
);
}
}

View File

@ -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<AuthService>().signOut();
runAppFlowy();
},
),
),
],
);
}
}

View File

@ -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<SelfHostUrlBottomSheet> createState() => _SelfHostUrlBottomSheetState();
}
class _SelfHostUrlBottomSheetState extends State<SelfHostUrlBottomSheet> {
final TextEditingController _textFieldController = TextEditingController();
final _formKey = GlobalKey<FormState>();
@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: <Widget>[
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),
);
}
}
}
}

View File

@ -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<SelfHostSettingGroup> createState() => _SelfHostSettingGroupState();
}
class _SelfHostSettingGroupState extends State<SelfHostSettingGroup> {
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,
);
},
);
},
),
],
);
},
);
}
}

View File

@ -82,7 +82,6 @@ class InitAppWidgetTask extends LaunchTask {
path: 'assets/translations', path: 'assets/translations',
fallbackLocale: const Locale('en'), fallbackLocale: const Locale('en'),
useFallbackTranslations: true, useFallbackTranslations: true,
saveLocale: false,
child: app, child: app,
), ),
); );

View File

@ -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/cloud/appflowy_cloud_page.dart';
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.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/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/color/color_picker_screen.dart';
import 'package:appflowy/plugins/base/emoji/emoji_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'; import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart';
@ -49,6 +50,7 @@ GoRouter generateRouter(Widget child) {
_mobileHomeSettingPageRoute(), _mobileHomeSettingPageRoute(),
_mobileSettingUserAgreementPageRoute(), _mobileSettingUserAgreementPageRoute(),
_mobileCloudSettingAppFlowyCloudPageRoute(), _mobileCloudSettingAppFlowyCloudPageRoute(),
_mobileLaunchSettingsPageRoute(),
// view page // view page
_mobileEditorScreenRoute(), _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() { GoRoute _mobileHomeTrashPageRoute() {
return GoRoute( return GoRoute(
parentNavigatorKey: AppGlobals.rootNavKey, parentNavigatorKey: AppGlobals.rootNavKey,

View File

@ -1,10 +1,12 @@
import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.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/material.dart';
import 'package:go_router/go_router.dart';
class MobileSignInScreen extends StatelessWidget { class MobileSignInScreen extends StatelessWidget {
const MobileSignInScreen({ const MobileSignInScreen({
@ -20,40 +22,57 @@ class MobileSignInScreen extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 40), padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 40),
child: Column( child: Column(
children: [ children: [
const Spacer( const Spacer(flex: 4),
flex: 4, _buildLogo(),
),
const FlowySvg(
FlowySvgs.flowy_logo_xl,
size: Size.square(64),
blendMode: null,
),
const VSpace(spacing * 2), const VSpace(spacing * 2),
// Welcome to _buildWelcomeText(),
FlowyText( _buildAppNameText(colorScheme),
const VSpace(spacing),
const Spacer(flex: 2),
const SignInAnonymousButton(),
const VSpace(spacing),
if (isAuthEnabled) ...[
_buildThirdPartySignInButtons(colorScheme),
const VSpace(spacing),
_buildSettingsButton(context),
],
if (!isAuthEnabled) const Spacer(flex: 2),
],
),
),
);
}
Widget _buildWelcomeText() {
return FlowyText(
LocaleKeys.welcomeTo.tr(), LocaleKeys.welcomeTo.tr(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
fontSize: 32, fontSize: 32,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), );
// AppFlowy }
FlowyText(
Widget _buildLogo() {
return const FlowySvg(
FlowySvgs.flowy_logo_xl,
size: Size.square(64),
blendMode: null,
);
}
Widget _buildAppNameText(ColorScheme colorScheme) {
return FlowyText(
LocaleKeys.appName.tr(), LocaleKeys.appName.tr(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
fontSize: 32, fontSize: 32,
color: const Color(0xFF00BCF0), color: const Color(0xFF00BCF0),
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), );
const VSpace(spacing), }
const Spacer(
flex: 2,
),
const SignInAnonymousButton(), Widget _buildThirdPartySignInButtons(ColorScheme colorScheme) {
const VSpace(spacing), return Column(
children: [
// if the cloud env is enabled, show the third-party sign in buttons.
if (isAuthEnabled) ...[
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -68,17 +87,24 @@ class MobileSignInScreen extends StatelessWidget {
const Expanded(child: Divider()), const Expanded(child: Divider()),
], ],
), ),
const VSpace(spacing), const VSpace(16),
const ThirdPartySignInButtons(), const ThirdPartySignInButtons(),
], ],
if (!isAuthEnabled) );
const Spacer( }
flex: 2,
), Widget _buildSettingsButton(BuildContext context) {
const VSpace(spacing), return FlowyButton(
], text: const FlowyText(
), 'settings',
textAlign: TextAlign.center,
fontSize: 12.0,
fontWeight: FontWeight.w500,
decoration: TextDecoration.underline,
), ),
onTap: () async {
context.push(MobileLaunchSettingsPage.routeName);
},
); );
} }
} }

View File

@ -59,6 +59,7 @@ class SignInAnonymousButton extends StatelessWidget {
LocaleKeys.signIn_loginStartWithAnonymous.tr(), LocaleKeys.signIn_loginStartWithAnonymous.tr(),
fontSize: 14, fontSize: 14,
color: Theme.of(context).colorScheme.onPrimary, color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.w500,
), ),
); );
} }

View File

@ -51,9 +51,10 @@ class MobileSignInOrLogoutButton extends StatelessWidget {
), ),
const HSpace(8), const HSpace(8),
], ],
Text( FlowyText(
labelText, labelText,
style: Theme.of(context).textTheme.titleSmall, fontSize: 14.0,
fontWeight: FontWeight.w500,
), ),
], ],
), ),

View File

@ -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. // 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 = final themeModeFromCubit =
context.read<AppearanceSettingsCubit>().state.themeMode; context.watch<AppearanceSettingsCubit>().state.themeMode;
final isDarkMode = themeModeFromCubit == ThemeMode.system final isDarkMode = themeModeFromCubit == ThemeMode.system
? MediaQuery.of(context).platformBrightness == Brightness.dark ? MediaQuery.of(context).platformBrightness == Brightness.dark

View File

@ -440,8 +440,8 @@
}, },
"mobile": { "mobile": {
"personalInfo": "Personal Information", "personalInfo": "Personal Information",
"username": "Username", "username": "User Name",
"usernameEmptyError": "Username cannot be empty", "usernameEmptyError": "User name cannot be empty",
"about": "About", "about": "About",
"pushNotifications": "Push Notifications", "pushNotifications": "Push Notifications",
"support": "Support", "support": "Support",