feat: support system default font family on desktop (#5279)

* fix: add permission check before selecting image in image block

* feat: use system default font on desktop

* fix: set appbar icon size to 30

* feat: add default font family on desktop
This commit is contained in:
Lucas.Xu 2024-05-07 19:44:00 +08:00 committed by GitHub
parent b4279f8004
commit 6220680ce0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 234 additions and 191 deletions

View File

@ -1,3 +1,4 @@
import 'package:appflowy/util/font_family_extension.dart';
import 'package:appflowy/workspace/application/appearance_defaults.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/settings_appearance.dart';
@ -82,8 +83,10 @@ void main() {
await tester.openSettingsPage(SettingsPage.appearance);
expect(
find.textContaining(DefaultAppearanceSettings.kDefaultFontFamily),
findsOneWidget,
find.textContaining(
DefaultAppearanceSettings.kDefaultFontFamily.fontFamilyDisplayName,
),
findsNWidgets(2),
);
});
});

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/log.dart';
@ -179,8 +178,8 @@ class DocumentPageStyleBloc
);
}
String _getSelectedFontFamily(Map layoutObject) {
return layoutObject[ViewExtKeys.fontKey] ?? builtInFontFamily();
String? _getSelectedFontFamily(Map layoutObject) {
return layoutObject[ViewExtKeys.fontKey];
}
(PageStyleCoverImageType, String colorValue) _getSelectedCover(

View File

@ -216,7 +216,11 @@ class _MobileViewPageState extends State<MobileViewPage> {
child: AppBarButton(
padding: EdgeInsets.zero,
onTap: (context) => context.pop(),
child: _buildImmersiveAppBarIcon(FlowySvgs.m_app_bar_back_s),
child: _buildImmersiveAppBarIcon(
FlowySvgs.m_app_bar_back_s,
30.0,
iconPadding: 6.0,
),
),
),
actions: actions,
@ -274,13 +278,13 @@ class _MobileViewPageState extends State<MobileViewPage> {
),
);
},
child: _buildImmersiveAppBarIcon(FlowySvgs.m_layout_s),
child: _buildImmersiveAppBarIcon(FlowySvgs.m_layout_s, 30.0),
);
}
Widget _buildAppBarMoreButton(ViewPB view) {
return AppBarButton(
padding: const EdgeInsets.only(left: 8, right: 16, top: 2, bottom: 2),
padding: const EdgeInsets.only(left: 8, right: 16),
onTap: (context) {
EditorNotification.exitEditing().post();
@ -292,49 +296,62 @@ class _MobileViewPageState extends State<MobileViewPage> {
builder: (_) => _buildAppBarMoreBottomSheet(context),
);
},
child: _buildImmersiveAppBarIcon(FlowySvgs.m_app_bar_more_s),
child: _buildImmersiveAppBarIcon(FlowySvgs.m_app_bar_more_s, 30.0),
);
}
Widget _buildImmersiveAppBarIcon(FlowySvgData icon) {
return ValueListenableBuilder(
valueListenable: _isImmersiveMode,
builder: (context, isImmersiveMode, child) {
return ValueListenableBuilder(
valueListenable: _appBarOpacity,
builder: (context, appBarOpacity, child) {
Color? color;
Widget _buildImmersiveAppBarIcon(
FlowySvgData icon,
double dimension, {
double iconPadding = 5.0,
}) {
assert(
dimension > 0.0 && dimension <= kToolbarHeight,
'dimension must be greater than 0, and less than or equal to kToolbarHeight',
);
return UnconstrainedBox(
child: SizedBox.square(
dimension: dimension,
child: ValueListenableBuilder(
valueListenable: _isImmersiveMode,
builder: (context, isImmersiveMode, child) {
return ValueListenableBuilder(
valueListenable: _appBarOpacity,
builder: (context, appBarOpacity, child) {
Color? color;
// if there's no cover or the cover is not immersive,
// make sure the app bar is always visible
if (!isImmersiveMode) {
color = null;
} else if (appBarOpacity < 0.99) {
color = Colors.white;
}
// if there's no cover or the cover is not immersive,
// make sure the app bar is always visible
if (!isImmersiveMode) {
color = null;
} else if (appBarOpacity < 0.99) {
color = Colors.white;
}
Widget child = Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: FlowySvg(
icon,
color: color,
),
Widget child = Container(
margin: EdgeInsets.all(iconPadding),
child: FlowySvg(
icon,
color: color,
),
);
if (isImmersiveMode && appBarOpacity <= 0.99) {
child = DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(dimension / 2.0),
color: Colors.black.withOpacity(0.2),
),
child: child,
);
}
return child;
},
);
if (isImmersiveMode && appBarOpacity <= 0.99) {
child = DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
color: Colors.black.withOpacity(0.2),
),
child: child,
);
}
return child;
},
);
},
),
),
);
}

View File

@ -3,7 +3,7 @@ import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_text_field.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/shared/google_fonts_extension.dart';
import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy/util/font_family_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:easy_localization/easy_localization.dart';
@ -13,7 +13,7 @@ import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
final List<String> _availableFonts = [
builtInFontFamily(),
defaultFontFamily,
...GoogleFonts.asMap().keys,
];
@ -106,16 +106,12 @@ class _FontSelectorState extends State<FontSelector> {
}
final fontFamilyName = availableFonts[index - 1];
final usingDefaultFontFamily = fontFamilyName == builtInFontFamily();
final usingDefaultFontFamily = fontFamilyName == defaultFontFamily;
final fontFamily = !usingDefaultFontFamily
? getGoogleFontSafely(fontFamilyName).fontFamily
: TextStyle(fontFamily: builtInFontFamily()).fontFamily;
: defaultFontFamily;
return FlowyOptionTile.checkbox(
// display the default font name if the font family name is empty
// or using the default font family
text: fontFamilyName.isNotEmpty && !usingDefaultFontFamily
? fontFamilyName.parseFontFamilyName()
: LocaleKeys.settings_appearance_fontFamily_defaultFont.tr(),
text: fontFamilyName.fontFamilyDisplayName,
isSelected: widget.selectedFontFamilyName == fontFamilyName,
showTopBorder: false,
onTap: () => widget.onFontFamilySelected(fontFamilyName),

View File

@ -3,8 +3,8 @@ import 'dart:async';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/util/font_family_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@ -22,9 +22,7 @@ class FontSetting extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final selectedFont = context.watch<AppearanceSettingsCubit>().state.font;
final name = selectedFont == builtInFontFamily()
? LocaleKeys.settings_appearance_fontFamily_defaultFont.tr()
: selectedFont;
final name = selectedFont.fontFamilyDisplayName;
return MobileSettingItem(
name: LocaleKeys.settings_appearance_fontFamily_label.tr(),
trailing: Row(

View File

@ -57,9 +57,9 @@ class DocumentAppearance {
class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
DocumentAppearanceCubit()
: super(
DocumentAppearance(
const DocumentAppearance(
fontSize: 16.0,
fontFamily: builtInFontFamily(),
fontFamily: defaultFontFamily,
codeFontFamily: builtInCodeFontFamily,
),
);
@ -69,7 +69,7 @@ class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
final fontSize =
prefs.getDouble(KVKeys.kDocumentAppearanceFontSize) ?? 16.0;
final fontFamily = prefs.getString(KVKeys.kDocumentAppearanceFontFamily) ??
builtInFontFamily();
defaultFontFamily;
final defaultTextDirection =
prefs.getString(KVKeys.kDocumentAppearanceDefaultTextDirection);

View File

@ -127,7 +127,7 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
BuildContext context,
DocumentImmersiveCoverState state,
) {
String? fontFamily = builtInFontFamily();
String? fontFamily = defaultFontFamily;
final documentFontFamily =
context.read<DocumentPageStyleBloc>().state.fontFamily;
if (documentFontFamily != null && fontFamily != documentFontFamily) {

View File

@ -1,6 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/permission/permission_checker.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -20,22 +22,28 @@ class UploadImageFileWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowyHover(
child: FlowyButton(
showDefaultBoxDecorationOnMobile: true,
text: Container(
margin: const EdgeInsets.all(4.0),
alignment: Alignment.center,
child: FlowyText(
LocaleKeys.document_imageBlock_upload_placeholder.tr(),
),
final child = FlowyButton(
showDefaultBoxDecorationOnMobile: true,
text: Container(
margin: const EdgeInsets.all(4.0),
alignment: Alignment.center,
child: FlowyText(
LocaleKeys.document_imageBlock_upload_placeholder.tr(),
),
onTap: _uploadImage,
),
onTap: () => _uploadImage(context),
);
if (PlatformExtension.isDesktopOrWeb) {
return FlowyHover(
child: child,
);
}
return child;
}
Future<void> _uploadImage() async {
Future<void> _uploadImage(BuildContext context) async {
if (PlatformExtension.isDesktopOrWeb) {
// on desktop, the users can pick a image file from folder
final result = await getIt<FilePickerService>().pickFiles(
@ -45,6 +53,12 @@ class UploadImageFileWidget extends StatelessWidget {
);
onPickFile(result?.files.firstOrNull?.path);
} else {
final photoPermission =
await PermissionChecker.checkPhotoPermission(context);
if (!photoPermission) {
Log.error('Has no permission to access the photo library');
return;
}
// on mobile, the users can pick a image file from camera or image library
final result = await ImagePicker().pickImage(source: ImageSource.gallery);
onPickFile(result?.path);

View File

@ -5,7 +5,7 @@ import 'package:appflowy/plugins/document/application/document_appearance_cubit.
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/shared/google_fonts_extension.dart';
import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy/util/font_family_extension.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

View File

@ -4,13 +4,12 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_cover_bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart';
import 'package:appflowy/shared/feedback_gesture_detector.dart';
import 'package:appflowy/startup/tasks/device_info_task.dart';
import 'package:appflowy/shared/permission/permission_checker.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
@ -18,11 +17,9 @@ import 'package:appflowy_result/appflowy_result.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class PageStyleCoverImage extends StatelessWidget {
PageStyleCoverImage({
@ -121,7 +118,8 @@ class PageStyleCoverImage extends StatelessWidget {
}
Future<void> _pickImage(BuildContext context) async {
final photoPermission = await _checkPhotoPermission(context);
final photoPermission =
await PermissionChecker.checkPhotoPermission(context);
if (!photoPermission) {
Log.error('Has no permission to access the photo library');
return;
@ -129,9 +127,7 @@ class PageStyleCoverImage extends StatelessWidget {
XFile? result;
try {
result = await _imagePicker.pickImage(
source: ImageSource.gallery,
);
result = await _imagePicker.pickImage(source: ImageSource.gallery);
} catch (e) {
Log.error('Error while picking image: $e');
return;
@ -224,54 +220,6 @@ class PageStyleCoverImage extends StatelessWidget {
},
);
}
Future<bool> _checkPhotoPermission(BuildContext context) async {
// check the permission first
final status = await Permission.photos.status;
// if the permission is permanently denied, we should open the app settings
if (status.isPermanentlyDenied && context.mounted) {
unawaited(
showFlowyMobileConfirmDialog(
context,
title: FlowyText.semibold(
LocaleKeys.pageStyle_photoPermissionTitle.tr(),
maxLines: 3,
textAlign: TextAlign.center,
),
content: FlowyText(
LocaleKeys.pageStyle_photoPermissionDescription.tr(),
maxLines: 5,
textAlign: TextAlign.center,
fontSize: 12.0,
),
actionAlignment: ConfirmDialogActionAlignment.vertical,
actionButtonTitle: LocaleKeys.pageStyle_openSettings.tr(),
actionButtonColor: Colors.blue,
cancelButtonTitle: LocaleKeys.pageStyle_doNotAllow.tr(),
cancelButtonColor: Colors.blue,
onActionButtonPressed: () {
openAppSettings();
},
),
);
return false;
} else if (status.isDenied) {
// https://github.com/Baseflow/flutter-permission-handler/issues/1262#issuecomment-2006340937
Permission permission = Permission.photos;
if (defaultTargetPlatform == TargetPlatform.android &&
ApplicationInfo.androidSDKVersion <= 32) {
permission = Permission.storage;
}
// if the permission is denied, we should request the permission
final newStatus = await permission.request();
if (newStatus.isDenied) {
return false;
}
}
return true;
}
}
class _UnsplashCover extends StatelessWidget {

View File

@ -5,6 +5,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_she
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart';
import 'package:appflowy/shared/feedback_gesture_detector.dart';
import 'package:appflowy/util/font_family_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -163,11 +164,8 @@ class _FontButton extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>(
builder: (context, state) {
String fontFamily = state.fontFamily ?? builtInFontFamily();
if (fontFamily == builtInFontFamily()) {
fontFamily =
LocaleKeys.settings_appearance_fontFamily_defaultFont.tr();
}
final fontFamilyDisplayName =
(state.fontFamily ?? defaultFontFamily).fontFamilyDisplayName;
return GestureDetector(
onTap: () => _showFontSelector(context),
behavior: HitTestBehavior.opaque,
@ -182,7 +180,7 @@ class _FontButton extends StatelessWidget {
const HSpace(16.0),
FlowyText(LocaleKeys.titleBar_font.tr()),
const Spacer(),
FlowyText(fontFamily),
FlowyText(fontFamilyDisplayName),
const HSpace(6.0),
const FlowySvg(FlowySvgs.m_page_style_arrow_right_s),
const HSpace(12.0),
@ -219,7 +217,7 @@ class _FontButton extends StatelessWidget {
child: FontSelector(
scrollController: controller,
selectedFontFamilyName:
state.fontFamily ?? builtInFontFamily(),
state.fontFamily ?? defaultFontFamily,
onFontFamilySelected: (fontFamilyName) {
context.read<DocumentPageStyleBloc>().add(
DocumentPageStyleEvent.updateFontFamily(

View File

@ -8,7 +8,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_too
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
import 'package:appflowy/shared/google_fonts_extension.dart';
import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy/util/font_family_extension.dart';
import 'package:appflowy/workspace/application/appearance_defaults.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
@ -92,7 +92,7 @@ class EditorStyleCustomizer {
final theme = Theme.of(context);
final fontSize = pageStyle.fontLayout.fontSize;
final lineHeight = pageStyle.lineHeightLayout.lineHeight;
final fontFamily = pageStyle.fontFamily ?? builtInFontFamily();
final fontFamily = pageStyle.fontFamily ?? defaultFontFamily;
final defaultTextDirection =
context.read<DocumentAppearanceCubit>().state.defaultTextDirection;
final baseTextStyle = this.baseTextStyle(fontFamily);
@ -178,7 +178,7 @@ class EditorStyleCustomizer {
TextStyle outlineBlockPlaceholderStyleBuilder() {
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
return TextStyle(
fontFamily: builtInFontFamily(),
fontFamily: defaultFontFamily,
fontSize: fontSize,
height: 1.5,
color: Theme.of(context).colorScheme.onBackground.withOpacity(0.6),
@ -219,7 +219,8 @@ class EditorStyleCustomizer {
try {
return getGoogleFontSafely(fontFamily, fontWeight: fontWeight);
} on Exception {
if ([builtInFontFamily(), builtInCodeFontFamily].contains(fontFamily)) {
if ([defaultFontFamily, fallbackFontFamily, builtInCodeFontFamily]
.contains(fontFamily)) {
return TextStyle(fontFamily: fontFamily, fontWeight: fontWeight);
}

View File

@ -0,0 +1,64 @@
// Check if the user has the required permission to access the device's
// - camera
// - storage
// - ...
import 'dart:async';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
import 'package:appflowy/startup/tasks/device_info_task.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
class PermissionChecker {
static Future<bool> checkPhotoPermission(BuildContext context) async {
// check the permission first
final status = await Permission.photos.status;
// if the permission is permanently denied, we should open the app settings
if (status.isPermanentlyDenied && context.mounted) {
unawaited(
showFlowyMobileConfirmDialog(
context,
title: FlowyText.semibold(
LocaleKeys.pageStyle_photoPermissionTitle.tr(),
maxLines: 3,
textAlign: TextAlign.center,
),
content: FlowyText(
LocaleKeys.pageStyle_photoPermissionDescription.tr(),
maxLines: 5,
textAlign: TextAlign.center,
fontSize: 12.0,
),
actionAlignment: ConfirmDialogActionAlignment.vertical,
actionButtonTitle: LocaleKeys.pageStyle_openSettings.tr(),
actionButtonColor: Colors.blue,
cancelButtonTitle: LocaleKeys.pageStyle_doNotAllow.tr(),
cancelButtonColor: Colors.blue,
onActionButtonPressed: () {
openAppSettings();
},
),
);
return false;
} else if (status.isDenied) {
// https://github.com/Baseflow/flutter-permission-handler/issues/1262#issuecomment-2006340937
Permission permission = Permission.photos;
if (defaultTargetPlatform == TargetPlatform.android &&
ApplicationInfo.androidSDKVersion <= 32) {
permission = Permission.storage;
}
// if the permission is denied, we should request the permission
final newStatus = await permission.request();
if (newStatus.isDenied) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,15 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/patterns/common_patterns.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:easy_localization/easy_localization.dart';
extension FontFamilyExtension on String {
String parseFontFamilyName() => replaceAll('_regular', '')
.replaceAllMapped(camelCaseRegex, (m) => ' ${m.group(0)}');
// display the default font name if the font family name is empty
// or using the default font family
String get fontFamilyDisplayName => isEmpty || this == defaultFontFamily
? LocaleKeys.settings_appearance_fontFamily_defaultFont.tr()
: parseFontFamilyName();
}

View File

@ -1,6 +0,0 @@
import 'package:appflowy/shared/patterns/common_patterns.dart';
extension GoogleFontsParser on String {
String parseFontFamilyName() => replaceAll('_regular', '')
.replaceAllMapped(camelCaseRegex, (m) => ' ${m.group(0)}');
}

View File

@ -1,9 +1,10 @@
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flutter/material.dart';
/// A class for the default appearance settings for the app
class DefaultAppearanceSettings {
static const kDefaultFontFamily = 'Poppins';
static const kDefaultFontFamily = defaultFontFamily;
static const kDefaultThemeMode = ThemeMode.system;
static const kDefaultThemeName = "Default";
static const kDefaultTheme = BuiltInTheme.defaultTheme;

View File

@ -1,28 +1,19 @@
import 'dart:io';
import 'package:appflowy/shared/google_fonts_extension.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flutter/material.dart';
String builtInFontFamily() {
if (PlatformExtension.isDesktopOrWeb) {
return 'Poppins';
}
// the default font family is empty, so we can use the default font family of the platform
// the system will choose the default font family of the platform
// iOS: San Francisco
// Android: Roboto
// Desktop: Based on the OS
const defaultFontFamily = '';
if (Platform.isIOS) {
return 'San Francisco';
}
if (Platform.isAndroid) {
return 'Roboto';
}
return 'Roboto';
}
// 'Poppins';
// the Poppins font is embedded in the app, so we can use it without GoogleFonts
// TODO(Lucas): after releasing version 0.5.6, remove it.
const fallbackFontFamily = 'Poppins';
const builtInCodeFontFamily = 'RobotoMono';
abstract class BaseAppearance {
@ -48,17 +39,15 @@ abstract class BaseAppearance {
letterSpacing = fontSize * (letterSpacing ?? 0.005);
final textStyle = TextStyle(
fontFamily: fontFamily,
fontFamily: fontFamily.isEmpty ? null : fontFamily,
fontSize: fontSize,
color: fontColor,
fontWeight: fontWeight,
fontFamilyFallback: [builtInFontFamily()],
letterSpacing: letterSpacing,
height: lineHeight,
);
// we embed Poppins font in the app, so we can use it without GoogleFonts
if (fontFamily == builtInFontFamily()) {
if (fontFamily == defaultFontFamily || fontFamily == fallbackFontFamily) {
return textStyle;
}

View File

@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/material.dart';
class DesktopAppearance extends BaseAppearance {
@override
@ -13,7 +12,6 @@ class DesktopAppearance extends BaseAppearance {
String fontFamily,
String codeFontFamily,
) {
assert(fontFamily.isNotEmpty);
assert(codeFontFamily.isNotEmpty);
final theme = brightness == Brightness.light

View File

@ -1,17 +1,17 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/shared/google_fonts_extension.dart';
import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy/util/font_family_extension.dart';
import 'package:appflowy/workspace/application/appearance_defaults.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
@ -83,7 +83,10 @@ class FontFamilyDropDown extends StatefulWidget {
}
class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
final List<String> availableFonts = GoogleFonts.asMap().keys.toList();
final List<String> availableFonts = [
defaultFontFamily,
...GoogleFonts.asMap().keys,
];
final ValueNotifier<String> query = ValueNotifier('');
@override
@ -94,10 +97,11 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
@override
Widget build(BuildContext context) {
final currentValue = widget.currentFontFamily.fontFamilyDisplayName;
return FlowySettingValueDropDown(
popoverKey: ThemeFontFamilySetting.popoverKey,
popoverController: widget.popoverController,
currentValue: widget.currentFontFamily.parseFontFamilyName(),
currentValue: currentValue,
onClose: () {
query.value = '';
widget.onClose?.call();
@ -168,8 +172,8 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
BuildContext context,
TextStyle style,
) {
final buttonFontFamily = style.fontFamily!.parseFontFamilyName();
final buttonFontFamily =
style.fontFamily?.parseFontFamilyName() ?? defaultFontFamily;
return Tooltip(
message: buttonFontFamily,
waitDuration: const Duration(milliseconds: 150),
@ -179,8 +183,8 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
child: FlowyButton(
onHover: (_) => FocusScope.of(context).unfocus(),
text: FlowyText.medium(
buttonFontFamily,
fontFamily: style.fontFamily!,
buttonFontFamily.fontFamilyDisplayName,
fontFamily: buttonFontFamily,
),
rightIcon:
buttonFontFamily == widget.currentFontFamily.parseFontFamilyName()
@ -190,15 +194,14 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
if (widget.onFontFamilyChanged != null) {
widget.onFontFamilyChanged!(buttonFontFamily);
} else {
final fontFamily = style.fontFamily!.parseFontFamilyName();
if (widget.currentFontFamily.parseFontFamilyName() !=
buttonFontFamily) {
context
.read<AppearanceSettingsCubit>()
.setFontFamily(fontFamily);
.setFontFamily(buttonFontFamily);
context
.read<DocumentAppearanceCubit>()
.syncFontFamily(fontFamily);
.syncFontFamily(buttonFontFamily);
}
}
PopoverContainer.of(context).close();

View File

@ -36,7 +36,7 @@ void main() {
AppTheme.fallback,
),
verify: (bloc) {
expect(bloc.state.font, builtInFontFamily());
expect(bloc.state.font, defaultFontFamily);
expect(bloc.state.monospaceFont, 'SF Mono');
expect(bloc.state.themeMode, ThemeMode.system);
},

View File

@ -27,7 +27,7 @@ void main() {
test('Initial state', () {
expect(cubit.state.fontSize, 16.0);
expect(cubit.state.fontFamily, builtInFontFamily());
expect(cubit.state.fontFamily, defaultFontFamily);
});
test('Fetch document appearance from SharedPreferences', () async {

View File

@ -1,7 +1,9 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -56,9 +58,9 @@ void main() {
value: documentAppearanceCubit,
),
],
child: Scaffold(
child: const Scaffold(
body: ThemeFontFamilySetting(
currentFontFamily: builtInFontFamily(),
currentFontFamily: defaultFontFamily,
),
),
),
@ -71,7 +73,10 @@ void main() {
await tester.pumpAndSettle();
// Verify the initial font family
expect(find.text(builtInFontFamily()), findsAtLeastNWidgets(1));
expect(
find.text(LocaleKeys.settings_appearance_fontFamily_defaultFont.tr()),
findsAtLeastNWidgets(1),
);
when(() => appearanceSettingsCubit.setFontFamily(any<String>()))
.thenAnswer((_) async {});
verifyNever(() => appearanceSettingsCubit.setFontFamily(any<String>()));

View File

@ -128,7 +128,7 @@ pub struct DocumentSettingsPB {
}
pub const APPEARANCE_DEFAULT_THEME: &str = "Default";
pub const APPEARANCE_DEFAULT_FONT: &str = "Poppins";
pub const APPEARANCE_DEFAULT_FONT: &str = ""; // Use system default font
pub const APPEARANCE_DEFAULT_MONOSPACE_FONT: &str = "SF Mono";
const APPEARANCE_RESET_AS_DEFAULT: bool = true;
const APPEARANCE_DEFAULT_IS_MENU_COLLAPSED: bool = false;