mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
b4279f8004
commit
6220680ce0
@ -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),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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';
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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)}');
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -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>()));
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user