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/appearance_defaults.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/settings_appearance.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/settings_appearance.dart';
@ -82,8 +83,10 @@ void main() {
await tester.openSettingsPage(SettingsPage.appearance); await tester.openSettingsPage(SettingsPage.appearance);
expect( expect(
find.textContaining(DefaultAppearanceSettings.kDefaultFontFamily), find.textContaining(
findsOneWidget, DefaultAppearanceSettings.kDefaultFontFamily.fontFamilyDisplayName,
),
findsNWidgets(2),
); );
}); });
}); });

View File

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

View File

@ -216,7 +216,11 @@ class _MobileViewPageState extends State<MobileViewPage> {
child: AppBarButton( child: AppBarButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
onTap: (context) => context.pop(), 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, 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) { Widget _buildAppBarMoreButton(ViewPB view) {
return AppBarButton( return AppBarButton(
padding: const EdgeInsets.only(left: 8, right: 16, top: 2, bottom: 2), padding: const EdgeInsets.only(left: 8, right: 16),
onTap: (context) { onTap: (context) {
EditorNotification.exitEditing().post(); EditorNotification.exitEditing().post();
@ -292,12 +296,23 @@ class _MobileViewPageState extends State<MobileViewPage> {
builder: (_) => _buildAppBarMoreBottomSheet(context), 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) { Widget _buildImmersiveAppBarIcon(
return ValueListenableBuilder( 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, valueListenable: _isImmersiveMode,
builder: (context, isImmersiveMode, child) { builder: (context, isImmersiveMode, child) {
return ValueListenableBuilder( return ValueListenableBuilder(
@ -314,7 +329,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
} }
Widget child = Container( Widget child = Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), margin: EdgeInsets.all(iconPadding),
child: FlowySvg( child: FlowySvg(
icon, icon,
color: color, color: color,
@ -324,7 +339,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
if (isImmersiveMode && appBarOpacity <= 0.99) { if (isImmersiveMode && appBarOpacity <= 0.99) {
child = DecoratedBox( child = DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22), borderRadius: BorderRadius.circular(dimension / 2.0),
color: Colors.black.withOpacity(0.2), color: Colors.black.withOpacity(0.2),
), ),
child: child, child: child,
@ -335,6 +350,8 @@ class _MobileViewPageState extends State<MobileViewPage> {
}, },
); );
}, },
),
),
); );
} }

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart'; 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/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:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart'; import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -20,8 +22,7 @@ class UploadImageFileWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FlowyHover( final child = FlowyButton(
child: FlowyButton(
showDefaultBoxDecorationOnMobile: true, showDefaultBoxDecorationOnMobile: true,
text: Container( text: Container(
margin: const EdgeInsets.all(4.0), margin: const EdgeInsets.all(4.0),
@ -30,12 +31,19 @@ class UploadImageFileWidget extends StatelessWidget {
LocaleKeys.document_imageBlock_upload_placeholder.tr(), LocaleKeys.document_imageBlock_upload_placeholder.tr(),
), ),
), ),
onTap: _uploadImage, onTap: () => _uploadImage(context),
), );
if (PlatformExtension.isDesktopOrWeb) {
return FlowyHover(
child: child,
); );
} }
Future<void> _uploadImage() async { return child;
}
Future<void> _uploadImage(BuildContext context) async {
if (PlatformExtension.isDesktopOrWeb) { if (PlatformExtension.isDesktopOrWeb) {
// on desktop, the users can pick a image file from folder // on desktop, the users can pick a image file from folder
final result = await getIt<FilePickerService>().pickFiles( final result = await getIt<FilePickerService>().pickFiles(
@ -45,6 +53,12 @@ class UploadImageFileWidget extends StatelessWidget {
); );
onPickFile(result?.files.firstOrNull?.path); onPickFile(result?.files.firstOrNull?.path);
} else { } 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 // on mobile, the users can pick a image file from camera or image library
final result = await ImagePicker().pickImage(source: ImageSource.gallery); final result = await ImagePicker().pickImage(source: ImageSource.gallery);
onPickFile(result?.path); 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/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/shared/google_fonts_extension.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:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.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/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.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/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/image_util.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.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_cover_bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.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/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/user/application/user_service.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.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: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:flowy_infra_ui/style_widget/snap_bar.dart'; import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class PageStyleCoverImage extends StatelessWidget { class PageStyleCoverImage extends StatelessWidget {
PageStyleCoverImage({ PageStyleCoverImage({
@ -121,7 +118,8 @@ class PageStyleCoverImage extends StatelessWidget {
} }
Future<void> _pickImage(BuildContext context) async { Future<void> _pickImage(BuildContext context) async {
final photoPermission = await _checkPhotoPermission(context); final photoPermission =
await PermissionChecker.checkPhotoPermission(context);
if (!photoPermission) { if (!photoPermission) {
Log.error('Has no permission to access the photo library'); Log.error('Has no permission to access the photo library');
return; return;
@ -129,9 +127,7 @@ class PageStyleCoverImage extends StatelessWidget {
XFile? result; XFile? result;
try { try {
result = await _imagePicker.pickImage( result = await _imagePicker.pickImage(source: ImageSource.gallery);
source: ImageSource.gallery,
);
} catch (e) { } catch (e) {
Log.error('Error while picking image: $e'); Log.error('Error while picking image: $e');
return; 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 { 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/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/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart';
import 'package:appflowy/shared/feedback_gesture_detector.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:appflowy/workspace/application/settings/appearance/base_appearance.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';
@ -163,11 +164,8 @@ class _FontButton extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>( return BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>(
builder: (context, state) { builder: (context, state) {
String fontFamily = state.fontFamily ?? builtInFontFamily(); final fontFamilyDisplayName =
if (fontFamily == builtInFontFamily()) { (state.fontFamily ?? defaultFontFamily).fontFamilyDisplayName;
fontFamily =
LocaleKeys.settings_appearance_fontFamily_defaultFont.tr();
}
return GestureDetector( return GestureDetector(
onTap: () => _showFontSelector(context), onTap: () => _showFontSelector(context),
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
@ -182,7 +180,7 @@ class _FontButton extends StatelessWidget {
const HSpace(16.0), const HSpace(16.0),
FlowyText(LocaleKeys.titleBar_font.tr()), FlowyText(LocaleKeys.titleBar_font.tr()),
const Spacer(), const Spacer(),
FlowyText(fontFamily), FlowyText(fontFamilyDisplayName),
const HSpace(6.0), const HSpace(6.0),
const FlowySvg(FlowySvgs.m_page_style_arrow_right_s), const FlowySvg(FlowySvgs.m_page_style_arrow_right_s),
const HSpace(12.0), const HSpace(12.0),
@ -219,7 +217,7 @@ class _FontButton extends StatelessWidget {
child: FontSelector( child: FontSelector(
scrollController: controller, scrollController: controller,
selectedFontFamilyName: selectedFontFamilyName:
state.fontFamily ?? builtInFontFamily(), state.fontFamily ?? defaultFontFamily,
onFontFamilySelected: (fontFamilyName) { onFontFamilySelected: (fontFamilyName) {
context.read<DocumentPageStyleBloc>().add( context.read<DocumentPageStyleBloc>().add(
DocumentPageStyleEvent.updateFontFamily( 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/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
import 'package:appflowy/shared/google_fonts_extension.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/appearance_defaults.dart';
import 'package:appflowy/workspace/application/settings/appearance/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/application/settings/appearance/base_appearance.dart';
@ -92,7 +92,7 @@ class EditorStyleCustomizer {
final theme = Theme.of(context); final theme = Theme.of(context);
final fontSize = pageStyle.fontLayout.fontSize; final fontSize = pageStyle.fontLayout.fontSize;
final lineHeight = pageStyle.lineHeightLayout.lineHeight; final lineHeight = pageStyle.lineHeightLayout.lineHeight;
final fontFamily = pageStyle.fontFamily ?? builtInFontFamily(); final fontFamily = pageStyle.fontFamily ?? defaultFontFamily;
final defaultTextDirection = final defaultTextDirection =
context.read<DocumentAppearanceCubit>().state.defaultTextDirection; context.read<DocumentAppearanceCubit>().state.defaultTextDirection;
final baseTextStyle = this.baseTextStyle(fontFamily); final baseTextStyle = this.baseTextStyle(fontFamily);
@ -178,7 +178,7 @@ class EditorStyleCustomizer {
TextStyle outlineBlockPlaceholderStyleBuilder() { TextStyle outlineBlockPlaceholderStyleBuilder() {
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize; final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
return TextStyle( return TextStyle(
fontFamily: builtInFontFamily(), fontFamily: defaultFontFamily,
fontSize: fontSize, fontSize: fontSize,
height: 1.5, height: 1.5,
color: Theme.of(context).colorScheme.onBackground.withOpacity(0.6), color: Theme.of(context).colorScheme.onBackground.withOpacity(0.6),
@ -219,7 +219,8 @@ class EditorStyleCustomizer {
try { try {
return getGoogleFontSafely(fontFamily, fontWeight: fontWeight); return getGoogleFontSafely(fontFamily, fontWeight: fontWeight);
} on Exception { } on Exception {
if ([builtInFontFamily(), builtInCodeFontFamily].contains(fontFamily)) { if ([defaultFontFamily, fallbackFontFamily, builtInCodeFontFamily]
.contains(fontFamily)) {
return TextStyle(fontFamily: fontFamily, fontWeight: fontWeight); 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:flowy_infra/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// A class for the default appearance settings for the app /// A class for the default appearance settings for the app
class DefaultAppearanceSettings { class DefaultAppearanceSettings {
static const kDefaultFontFamily = 'Poppins'; static const kDefaultFontFamily = defaultFontFamily;
static const kDefaultThemeMode = ThemeMode.system; static const kDefaultThemeMode = ThemeMode.system;
static const kDefaultThemeName = "Default"; static const kDefaultThemeName = "Default";
static const kDefaultTheme = BuiltInTheme.defaultTheme; 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/shared/google_fonts_extension.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
String builtInFontFamily() { // the default font family is empty, so we can use the default font family of the platform
if (PlatformExtension.isDesktopOrWeb) { // the system will choose the default font family of the platform
return 'Poppins'; // iOS: San Francisco
} // Android: Roboto
// Desktop: Based on the OS
const defaultFontFamily = '';
if (Platform.isIOS) { // the Poppins font is embedded in the app, so we can use it without GoogleFonts
return 'San Francisco'; // TODO(Lucas): after releasing version 0.5.6, remove it.
} const fallbackFontFamily = 'Poppins';
if (Platform.isAndroid) {
return 'Roboto';
}
return 'Roboto';
}
// 'Poppins';
const builtInCodeFontFamily = 'RobotoMono'; const builtInCodeFontFamily = 'RobotoMono';
abstract class BaseAppearance { abstract class BaseAppearance {
@ -48,17 +39,15 @@ abstract class BaseAppearance {
letterSpacing = fontSize * (letterSpacing ?? 0.005); letterSpacing = fontSize * (letterSpacing ?? 0.005);
final textStyle = TextStyle( final textStyle = TextStyle(
fontFamily: fontFamily, fontFamily: fontFamily.isEmpty ? null : fontFamily,
fontSize: fontSize, fontSize: fontSize,
color: fontColor, color: fontColor,
fontWeight: fontWeight, fontWeight: fontWeight,
fontFamilyFallback: [builtInFontFamily()],
letterSpacing: letterSpacing, letterSpacing: letterSpacing,
height: lineHeight, height: lineHeight,
); );
// we embed Poppins font in the app, so we can use it without GoogleFonts if (fontFamily == defaultFontFamily || fontFamily == fallbackFontFamily) {
if (fontFamily == builtInFontFamily()) {
return textStyle; 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:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/material.dart';
class DesktopAppearance extends BaseAppearance { class DesktopAppearance extends BaseAppearance {
@override @override
@ -13,7 +12,6 @@ class DesktopAppearance extends BaseAppearance {
String fontFamily, String fontFamily,
String codeFontFamily, String codeFontFamily,
) { ) {
assert(fontFamily.isNotEmpty);
assert(codeFontFamily.isNotEmpty); assert(codeFontFamily.isNotEmpty);
final theme = brightness == Brightness.light 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/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/shared/google_fonts_extension.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/appearance_defaults.dart';
import 'package:appflowy/workspace/application/settings/appearance/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_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.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';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
@ -83,7 +83,10 @@ class FontFamilyDropDown extends StatefulWidget {
} }
class _FontFamilyDropDownState extends State<FontFamilyDropDown> { 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(''); final ValueNotifier<String> query = ValueNotifier('');
@override @override
@ -94,10 +97,11 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final currentValue = widget.currentFontFamily.fontFamilyDisplayName;
return FlowySettingValueDropDown( return FlowySettingValueDropDown(
popoverKey: ThemeFontFamilySetting.popoverKey, popoverKey: ThemeFontFamilySetting.popoverKey,
popoverController: widget.popoverController, popoverController: widget.popoverController,
currentValue: widget.currentFontFamily.parseFontFamilyName(), currentValue: currentValue,
onClose: () { onClose: () {
query.value = ''; query.value = '';
widget.onClose?.call(); widget.onClose?.call();
@ -168,8 +172,8 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
BuildContext context, BuildContext context,
TextStyle style, TextStyle style,
) { ) {
final buttonFontFamily = style.fontFamily!.parseFontFamilyName(); final buttonFontFamily =
style.fontFamily?.parseFontFamilyName() ?? defaultFontFamily;
return Tooltip( return Tooltip(
message: buttonFontFamily, message: buttonFontFamily,
waitDuration: const Duration(milliseconds: 150), waitDuration: const Duration(milliseconds: 150),
@ -179,8 +183,8 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
child: FlowyButton( child: FlowyButton(
onHover: (_) => FocusScope.of(context).unfocus(), onHover: (_) => FocusScope.of(context).unfocus(),
text: FlowyText.medium( text: FlowyText.medium(
buttonFontFamily, buttonFontFamily.fontFamilyDisplayName,
fontFamily: style.fontFamily!, fontFamily: buttonFontFamily,
), ),
rightIcon: rightIcon:
buttonFontFamily == widget.currentFontFamily.parseFontFamilyName() buttonFontFamily == widget.currentFontFamily.parseFontFamilyName()
@ -190,15 +194,14 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
if (widget.onFontFamilyChanged != null) { if (widget.onFontFamilyChanged != null) {
widget.onFontFamilyChanged!(buttonFontFamily); widget.onFontFamilyChanged!(buttonFontFamily);
} else { } else {
final fontFamily = style.fontFamily!.parseFontFamilyName();
if (widget.currentFontFamily.parseFontFamilyName() != if (widget.currentFontFamily.parseFontFamilyName() !=
buttonFontFamily) { buttonFontFamily) {
context context
.read<AppearanceSettingsCubit>() .read<AppearanceSettingsCubit>()
.setFontFamily(fontFamily); .setFontFamily(buttonFontFamily);
context context
.read<DocumentAppearanceCubit>() .read<DocumentAppearanceCubit>()
.syncFontFamily(fontFamily); .syncFontFamily(buttonFontFamily);
} }
} }
PopoverContainer.of(context).close(); PopoverContainer.of(context).close();

View File

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

View File

@ -27,7 +27,7 @@ void main() {
test('Initial state', () { test('Initial state', () {
expect(cubit.state.fontSize, 16.0); expect(cubit.state.fontSize, 16.0);
expect(cubit.state.fontFamily, builtInFontFamily()); expect(cubit.state.fontFamily, defaultFontFamily);
}); });
test('Fetch document appearance from SharedPreferences', () async { 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/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/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/application/settings/appearance/base_appearance.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.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:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -56,9 +58,9 @@ void main() {
value: documentAppearanceCubit, value: documentAppearanceCubit,
), ),
], ],
child: Scaffold( child: const Scaffold(
body: ThemeFontFamilySetting( body: ThemeFontFamilySetting(
currentFontFamily: builtInFontFamily(), currentFontFamily: defaultFontFamily,
), ),
), ),
), ),
@ -71,7 +73,10 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Verify the initial font family // 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>())) when(() => appearanceSettingsCubit.setFontFamily(any<String>()))
.thenAnswer((_) async {}); .thenAnswer((_) async {});
verifyNever(() => appearanceSettingsCubit.setFontFamily(any<String>())); 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_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"; pub const APPEARANCE_DEFAULT_MONOSPACE_FONT: &str = "SF Mono";
const APPEARANCE_RESET_AS_DEFAULT: bool = true; const APPEARANCE_RESET_AS_DEFAULT: bool = true;
const APPEARANCE_DEFAULT_IS_MENU_COLLAPSED: bool = false; const APPEARANCE_DEFAULT_IS_MENU_COLLAPSED: bool = false;