feat: customize text font (#3467)

* feat: update UI in settings page

* feat: customzing font in document page

* fix: flutter analyze and format issues
This commit is contained in:
Lucas.Xu 2023-09-21 09:12:25 +08:00 committed by GitHub
parent 4b9b723521
commit 9c59e1487e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 334 additions and 197 deletions

View File

@ -80,7 +80,8 @@ void main() {
await tester.openSettingsPage(SettingsPage.files); await tester.openSettingsPage(SettingsPage.files);
await tester.openSettingsPage(SettingsPage.appearance); await tester.openSettingsPage(SettingsPage.appearance);
expect(find.textContaining(DefaultAppearanceSettings.kDefaultFontFamily), expect(
find.textContaining(DefaultAppearanceSettings.kDefaultFontFamily),
findsOneWidget, findsOneWidget,
); );
}); });

View File

@ -12,8 +12,11 @@ class MyMockClient extends Mock implements http.Client {
final requestType = request.method; final requestType = request.method;
final requestUri = request.url; final requestUri = request.url;
if (requestType == 'POST' && requestUri == OpenAIRequestType.textCompletion.uri) { if (requestType == 'POST' &&
final responseHeaders = <String, String>{'content-type': 'text/event-stream'}; requestUri == OpenAIRequestType.textCompletion.uri) {
final responseHeaders = <String, String>{
'content-type': 'text/event-stream'
};
final responseBody = Stream.fromIterable([ final responseBody = Stream.fromIterable([
utf8.encode( utf8.encode(
'{ "choices": [{"text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula ", "index": 0, "logprobs": null, "finish_reason": null}]}', '{ "choices": [{"text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula ", "index": 0, "logprobs": null, "finish_reason": null}]}',
@ -51,7 +54,9 @@ class MockOpenAIRepository extends HttpOpenAIRepository {
var previousSyntax = ''; var previousSyntax = '';
if (response.statusCode == 200) { if (response.statusCode == 200) {
await for (final chunk in response.stream.transform(const Utf8Decoder()).transform(const LineSplitter())) { await for (final chunk in response.stream
.transform(const Utf8Decoder())
.transform(const LineSplitter())) {
await onStart(); await onStart();
final data = chunk.trim().split('data: '); final data = chunk.trim().split('data: ');
if (data[0] != '[DONE]') { if (data[0] != '[DONE]') {

View File

@ -382,8 +382,10 @@ Widget? _buildHeaderIcon(GroupData customData) {
case FieldType.Checkbox: case FieldType.Checkbox:
final group = customData.asCheckboxGroup()!; final group = customData.asCheckboxGroup()!;
if (group.isCheck) { if (group.isCheck) {
widget = widget = const FlowySvg(
const FlowySvg(FlowySvgs.check_filled_s, blendMode: BlendMode.dst,); FlowySvgs.check_filled_s,
blendMode: BlendMode.dst,
);
} else { } else {
widget = const FlowySvg(FlowySvgs.uncheck_s); widget = const FlowySvg(FlowySvgs.uncheck_s);
} }

View File

@ -75,8 +75,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
alignToolbarItem, alignToolbarItem,
buildTextColorItem(), buildTextColorItem(),
buildHighlightColorItem(), buildHighlightColorItem(),
// TODO: enable it in version 0.3.3 customizeFontToolbarItem,
// ...textDirectionItems, ...textDirectionItems,
]; ];
late final List<SelectionMenuItem> slashMenuItems; late final List<SelectionMenuItem> slashMenuItems;

View File

@ -61,13 +61,10 @@ SelectionMenuItem calloutItem = SelectionMenuItem.node(
// building the callout block widget // building the callout block widget
class CalloutBlockComponentBuilder extends BlockComponentBuilder { class CalloutBlockComponentBuilder extends BlockComponentBuilder {
CalloutBlockComponentBuilder({ CalloutBlockComponentBuilder({
this.configuration = const BlockComponentConfiguration(), super.configuration,
required this.defaultColor, required this.defaultColor,
}); });
@override
final BlockComponentConfiguration configuration;
final Color defaultColor; final Color defaultColor;
@override @override

View File

@ -51,13 +51,10 @@ SelectionMenuItem codeBlockItem = SelectionMenuItem.node(
class CodeBlockComponentBuilder extends BlockComponentBuilder { class CodeBlockComponentBuilder extends BlockComponentBuilder {
CodeBlockComponentBuilder({ CodeBlockComponentBuilder({
this.configuration = const BlockComponentConfiguration(), super.configuration,
this.padding = const EdgeInsets.all(0), this.padding = const EdgeInsets.all(0),
}); });
@override
final BlockComponentConfiguration configuration;
final EdgeInsets padding; final EdgeInsets padding;
@override @override

View File

@ -17,12 +17,9 @@ class DatabaseBlockKeys {
class DatabaseViewBlockComponentBuilder extends BlockComponentBuilder { class DatabaseViewBlockComponentBuilder extends BlockComponentBuilder {
DatabaseViewBlockComponentBuilder({ DatabaseViewBlockComponentBuilder({
this.configuration = const BlockComponentConfiguration(), super.configuration,
}); });
@override
final BlockComponentConfiguration configuration;
@override @override
BlockComponentWidget build(BlockComponentContext blockComponentContext) { BlockComponentWidget build(BlockComponentContext blockComponentContext) {
final node = blockComponentContext.node; final node = blockComponentContext.node;

View File

@ -8,26 +8,26 @@ import 'emoji_picker.dart';
/// Config for customizations /// Config for customizations
class Config { class Config {
/// Constructor /// Constructor
const Config( const Config({
{this.columns = 7, this.columns = 7,
this.emojiSizeMax = 32.0, this.emojiSizeMax = 32.0,
this.verticalSpacing = 0, this.verticalSpacing = 0,
this.horizontalSpacing = 0, this.horizontalSpacing = 0,
this.initCategory = Category.RECENT, this.initCategory = Category.RECENT,
this.bgColor = const Color(0xFFEBEFF2), this.bgColor = const Color(0xFFEBEFF2),
this.indicatorColor = Colors.blue, this.indicatorColor = Colors.blue,
this.iconColor = Colors.grey, this.iconColor = Colors.grey,
this.iconColorSelected = Colors.blue, this.iconColorSelected = Colors.blue,
this.progressIndicatorColor = Colors.blue, this.progressIndicatorColor = Colors.blue,
this.backspaceColor = Colors.blue, this.backspaceColor = Colors.blue,
this.showRecentsTab = true, this.showRecentsTab = true,
this.recentsLimit = 28, this.recentsLimit = 28,
this.noRecentsText = 'No Recents', this.noRecentsText = 'No Recents',
this.noRecentsStyle = this.noRecentsStyle = const TextStyle(fontSize: 20, color: Colors.black26),
const TextStyle(fontSize: 20, color: Colors.black26), this.tabIndicatorAnimDuration = kTabScrollDuration,
this.tabIndicatorAnimDuration = kTabScrollDuration, this.categoryIcons = const CategoryIcons(),
this.categoryIcons = const CategoryIcons(), this.buttonMode = ButtonMode.MATERIAL,
this.buttonMode = ButtonMode.MATERIAL,}); });
/// Number of emojis per row /// Number of emojis per row
final int columns; final int columns;

View File

@ -27,14 +27,16 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
@override @override
void initState() { void initState() {
var initCategory = widget.state.categoryEmoji.indexWhere( var initCategory = widget.state.categoryEmoji.indexWhere(
(element) => element.category == widget.config.initCategory,); (element) => element.category == widget.config.initCategory,
);
if (initCategory == -1) { if (initCategory == -1) {
initCategory = 0; initCategory = 0;
} }
_tabController = TabController( _tabController = TabController(
initialIndex: initCategory, initialIndex: initCategory,
length: widget.state.categoryEmoji.length, length: widget.state.categoryEmoji.length,
vsync: this,); vsync: this,
);
_pageController = PageController(initialPage: initCategory); _pageController = PageController(initialPage: initCategory);
_emojiFocusNode.requestFocus(); _emojiFocusNode.requestFocus();
@ -72,14 +74,15 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
return Material( return Material(
type: MaterialType.transparency, type: MaterialType.transparency,
child: IconButton( child: IconButton(
padding: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.only(bottom: 2),
icon: Icon( icon: Icon(
Icons.backspace, Icons.backspace,
color: widget.config.backspaceColor, color: widget.config.backspaceColor,
), ),
onPressed: () { onPressed: () {
widget.state.onBackspacePressed!(); widget.state.onBackspacePressed!();
},), },
),
); );
} }
return Container(); return Container();
@ -160,8 +163,12 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
: widget.state.categoryEmoji : widget.state.categoryEmoji
.asMap() .asMap()
.entries .entries
.map<Widget>((item) => _buildCategory( .map<Widget>(
item.value.category, emojiSize,),) (item) => _buildCategory(
item.value.category,
emojiSize,
),
)
.toList(), .toList(),
), ),
), ),
@ -206,8 +213,10 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
); );
} }
Widget _buildButtonWidget( Widget _buildButtonWidget({
{required VoidCallback onPressed, required Widget child,}) { required VoidCallback onPressed,
required Widget child,
}) {
if (widget.config.buttonMode == ButtonMode.MATERIAL) { if (widget.config.buttonMode == ButtonMode.MATERIAL) {
return InkWell( return InkWell(
onTap: onPressed, onTap: onPressed,
@ -266,29 +275,31 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
Emoji emoji, Emoji emoji,
) { ) {
return _buildButtonWidget( return _buildButtonWidget(
onPressed: () { onPressed: () {
widget.state.onEmojiSelected(categoryEmoji.category, emoji); widget.state.onEmojiSelected(categoryEmoji.category, emoji);
}, },
child: FittedBox( child: FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text( child: Text(
emoji.emoji, emoji.emoji,
textScaleFactor: 1.0, textScaleFactor: 1.0,
style: TextStyle( style: TextStyle(
fontSize: emojiSize, fontSize: emojiSize,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
),
), ),
),); ),
),
);
} }
Widget _buildNoRecent() { Widget _buildNoRecent() {
return Center( return Center(
child: FlowyText.regular( child: FlowyText.regular(
widget.config.noRecentsText, widget.config.noRecentsText,
color: Theme.of(context).colorScheme.tertiary.withAlpha(77), color: Theme.of(context).colorScheme.tertiary.withAlpha(77),
fontSize: widget.config.noRecentsStyle.fontSize, fontSize: widget.config.noRecentsStyle.fontSize,
textAlign: TextAlign.center, textAlign: TextAlign.center,
),); ),
);
} }
} }

View File

@ -190,30 +190,49 @@ class EmojiPickerState extends State<EmojiPicker> {
categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap)); categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap));
} }
categoryEmoji.addAll([ categoryEmoji.addAll([
CategoryEmoji(Category.SMILEYS,
await _getAvailableEmojis(emoji_list.smileys, title: 'smileys'),),
CategoryEmoji(Category.ANIMALS,
await _getAvailableEmojis(emoji_list.animals, title: 'animals'),),
CategoryEmoji(Category.FOODS,
await _getAvailableEmojis(emoji_list.foods, title: 'foods'),),
CategoryEmoji( CategoryEmoji(
Category.ACTIVITIES, Category.SMILEYS,
await _getAvailableEmojis(emoji_list.activities, await _getAvailableEmojis(emoji_list.smileys, title: 'smileys'),
title: 'activities',),), ),
CategoryEmoji(Category.TRAVEL, CategoryEmoji(
await _getAvailableEmojis(emoji_list.travel, title: 'travel'),), Category.ANIMALS,
CategoryEmoji(Category.OBJECTS, await _getAvailableEmojis(emoji_list.animals, title: 'animals'),
await _getAvailableEmojis(emoji_list.objects, title: 'objects'),), ),
CategoryEmoji(Category.SYMBOLS, CategoryEmoji(
await _getAvailableEmojis(emoji_list.symbols, title: 'symbols'),), Category.FOODS,
CategoryEmoji(Category.FLAGS, await _getAvailableEmojis(emoji_list.foods, title: 'foods'),
await _getAvailableEmojis(emoji_list.flags, title: 'flags'),) ),
CategoryEmoji(
Category.ACTIVITIES,
await _getAvailableEmojis(
emoji_list.activities,
title: 'activities',
),
),
CategoryEmoji(
Category.TRAVEL,
await _getAvailableEmojis(emoji_list.travel, title: 'travel'),
),
CategoryEmoji(
Category.OBJECTS,
await _getAvailableEmojis(emoji_list.objects, title: 'objects'),
),
CategoryEmoji(
Category.SYMBOLS,
await _getAvailableEmojis(emoji_list.symbols, title: 'symbols'),
),
CategoryEmoji(
Category.FLAGS,
await _getAvailableEmojis(emoji_list.flags, title: 'flags'),
)
]); ]);
} }
// Get available emoji for given category title // Get available emoji for given category title
Future<List<Emoji>> _getAvailableEmojis(Map<String, String> map, Future<List<Emoji>> _getAvailableEmojis(
{required String title,}) async { Map<String, String> map, {
required String title,
}) async {
Map<String, String>? newMap; Map<String, String>? newMap;
// Get Emojis cached locally if available // Get Emojis cached locally if available
@ -236,15 +255,18 @@ class EmojiPickerState extends State<EmojiPicker> {
// Check if emoji is available on current platform // Check if emoji is available on current platform
Future<Map<String, String>?> _getPlatformAvailableEmoji( Future<Map<String, String>?> _getPlatformAvailableEmoji(
Map<String, String> emoji,) async { Map<String, String> emoji,
) async {
if (Platform.isAndroid) { if (Platform.isAndroid) {
Map<String, String>? filtered = {}; Map<String, String>? filtered = {};
const delimiter = '|'; const delimiter = '|';
try { try {
final entries = emoji.values.join(delimiter); final entries = emoji.values.join(delimiter);
final keys = emoji.keys.join(delimiter); final keys = emoji.keys.join(delimiter);
final result = (await platform.invokeMethod<String>('checkAvailability', final result = (await platform.invokeMethod<String>(
{'emojiKeys': keys, 'emojiEntries': entries},)) as String; 'checkAvailability',
{'emojiKeys': keys, 'emojiEntries': entries},
)) as String;
final resultKeys = result.split(delimiter); final resultKeys = result.split(delimiter);
for (var i = 0; i < resultKeys.length; i++) { for (var i = 0; i < resultKeys.length; i++) {
filtered[resultKeys[i]] = emoji[resultKeys[i]]!; filtered[resultKeys[i]] = emoji[resultKeys[i]]!;
@ -272,7 +294,9 @@ class EmojiPickerState extends State<EmojiPicker> {
// Stores filtered emoji locally for faster access next time // Stores filtered emoji locally for faster access next time
Future<void> _cacheFilteredEmojis( Future<void> _cacheFilteredEmojis(
String title, Map<String, String> emojis,) async { String title,
Map<String, String> emojis,
) async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final emojiJson = jsonEncode(emojis); final emojiJson = jsonEncode(emojis);
prefs.setString(title, emojiJson); prefs.setString(title, emojiJson);
@ -305,7 +329,9 @@ class EmojiPickerState extends State<EmojiPicker> {
recentEmoji.sort((a, b) => b.counter - a.counter); recentEmoji.sort((a, b) => b.counter - a.counter);
// Limit entries to recentsLimit // Limit entries to recentsLimit
recentEmoji = recentEmoji.sublist( recentEmoji = recentEmoji.sublist(
0, min(widget.config.recentsLimit, recentEmoji.length),); 0,
min(widget.config.recentsLimit, recentEmoji.length),
);
// save locally // save locally
prefs.setString('recent', jsonEncode(recentEmoji)); prefs.setString('recent', jsonEncode(recentEmoji));
} }

View File

@ -1,4 +1,3 @@
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -0,0 +1,43 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flutter/material.dart';
final customizeFontToolbarItem = ToolbarItem(
id: 'editor.font',
group: 4,
isActive: onlyShowInTextType,
builder: (context, editorState, highlightColor) {
final selection = editorState.selection!;
final popoverController = PopoverController();
return MouseRegion(
cursor: SystemMouseCursors.click,
child: FontFamilyDropDown(
currentFontFamily: '',
popoverController: popoverController,
onOpen: () => keepEditorFocusNotifier.value += 1,
onClose: () => keepEditorFocusNotifier.value -= 1,
onFontFamilyChanged: (fontFamily) async {
await popoverController.close();
try {
await editorState.formatDelta(selection, {
AppFlowyRichTextKeys.fontFamily: fontFamily,
});
} catch (e) {
Log.error('Failed to set font family: $e');
}
},
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 4.0),
child: FlowySvg(
FlowySvgs.font_family_s,
size: Size.square(16.0),
color: Colors.white,
),
),
),
);
},
);

View File

@ -54,12 +54,9 @@ SelectionMenuItem mathEquationItem = SelectionMenuItem.node(
class MathEquationBlockComponentBuilder extends BlockComponentBuilder { class MathEquationBlockComponentBuilder extends BlockComponentBuilder {
MathEquationBlockComponentBuilder({ MathEquationBlockComponentBuilder({
this.configuration = const BlockComponentConfiguration(), super.configuration,
}); });
@override
final BlockComponentConfiguration configuration;
@override @override
BlockComponentWidget build(BlockComponentContext blockComponentContext) { BlockComponentWidget build(BlockComponentContext blockComponentContext) {
final node = blockComponentContext.node; final node = blockComponentContext.node;

View File

@ -32,12 +32,9 @@ Node outlineBlockNode() {
class OutlineBlockComponentBuilder extends BlockComponentBuilder { class OutlineBlockComponentBuilder extends BlockComponentBuilder {
OutlineBlockComponentBuilder({ OutlineBlockComponentBuilder({
this.configuration = const BlockComponentConfiguration(), super.configuration,
}); });
@override
final BlockComponentConfiguration configuration;
@override @override
BlockComponentWidget build(BlockComponentContext blockComponentContext) { BlockComponentWidget build(BlockComponentContext blockComponentContext) {
final node = blockComponentContext.node; final node = blockComponentContext.node;

View File

@ -14,6 +14,7 @@ export 'database/inline_database_menu_item.dart';
export 'database/referenced_database_menu_item.dart'; export 'database/referenced_database_menu_item.dart';
export 'emoji_picker/emoji_menu_item.dart'; export 'emoji_picker/emoji_menu_item.dart';
export 'extensions/flowy_tint_extension.dart'; export 'extensions/flowy_tint_extension.dart';
export 'font/customize_font_toolbar_item.dart';
export 'header/cover_editor_bloc.dart'; export 'header/cover_editor_bloc.dart';
export 'header/custom_cover_picker.dart'; export 'header/custom_cover_picker.dart';
export 'header/document_header_node_widget.dart'; export 'header/document_header_node_widget.dart';

View File

@ -55,13 +55,10 @@ SelectionMenuItem toggleListBlockItem = SelectionMenuItem.node(
class ToggleListBlockComponentBuilder extends BlockComponentBuilder { class ToggleListBlockComponentBuilder extends BlockComponentBuilder {
ToggleListBlockComponentBuilder({ ToggleListBlockComponentBuilder({
this.configuration = const BlockComponentConfiguration(), super.configuration,
this.padding = const EdgeInsets.all(0), this.padding = const EdgeInsets.all(0),
}); });
@override
final BlockComponentConfiguration configuration;
final EdgeInsets padding; final EdgeInsets padding;
@override @override

View File

@ -2,6 +2,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_mat
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -193,6 +194,15 @@ class EditorStyleCustomizer {
return textSpan; return textSpan;
} }
// try to refresh font here.
if (attributes.fontFamily != null) {
try {
GoogleFonts.getFont(attributes.fontFamily!.parseFontFamilyName());
} catch (e) {
// ignore
}
}
// customize the inline mention block, like inline page // customize the inline mention block, like inline page
final mention = attributes[MentionBlockKeys.mention] as Map?; final mention = attributes[MentionBlockKeys.mention] as Map?;
if (mention != null) { if (mention != null) {

View File

@ -0,0 +1,7 @@
extension GoogleFontsParser on String {
String parseFontFamilyName() {
final camelCase = RegExp('(?<=[a-z])[A-Z]');
return replaceAll('_regular', '')
.replaceAllMapped(camelCase, (m) => ' ${m.group(0)}');
}
}

View File

@ -300,10 +300,6 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
ThemeData get lightTheme => _getThemeData(Brightness.light); ThemeData get lightTheme => _getThemeData(Brightness.light);
ThemeData get darkTheme => _getThemeData(Brightness.dark); ThemeData get darkTheme => _getThemeData(Brightness.dark);
// only support LTR layout in version 0.3.2, enable it in version 0.3.3
LayoutDirectionPB get layoutDirectionPB => LayoutDirectionPB.LTRLayout;
TextDirectionPB get textDirectionPB => TextDirectionPB.LTR;
ThemeData _getThemeData(Brightness brightness) { ThemeData _getThemeData(Brightness brightness) {
// Poppins and SF Mono are not well supported in some languages, so use the // Poppins and SF Mono are not well supported in some languages, so use the
// built-in font for the following languages. // built-in font for the following languages.

View File

@ -1,8 +1,10 @@
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/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy/workspace/application/appearance.dart'; import 'package:appflowy/workspace/application/appearance.dart';
import 'package:appflowy/workspace/application/appearance_defaults.dart'; import 'package:appflowy/workspace/application/appearance_defaults.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_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -29,9 +31,6 @@ class ThemeFontFamilySetting extends StatefulWidget {
} }
class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> { class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
final List<String> availableFonts = GoogleFonts.asMap().keys.toList();
final ValueNotifier<String> query = ValueNotifier('');
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ThemeSettingEntryTemplateWidget( return ThemeSettingEntryTemplateWidget(
@ -44,61 +43,101 @@ class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
.syncFontFamily(DefaultAppearanceSettings.kDefaultFontFamily); .syncFontFamily(DefaultAppearanceSettings.kDefaultFontFamily);
}, },
trailing: [ trailing: [
ThemeValueDropDown( FontFamilyDropDown(
popoverKey: ThemeFontFamilySetting.popoverKey, currentFontFamily: widget.currentFontFamily,
currentValue: parseFontFamilyName(widget.currentFontFamily), )
onClose: () { ],
query.value = ''; );
}, }
popupBuilder: (_) => CustomScrollView( }
shrinkWrap: true,
slivers: [ class FontFamilyDropDown extends StatefulWidget {
SliverPadding( const FontFamilyDropDown({
padding: const EdgeInsets.only(right: 8), super.key,
sliver: SliverToBoxAdapter( required this.currentFontFamily,
child: FlowyTextField( this.onOpen,
key: ThemeFontFamilySetting.textFieldKey, this.onClose,
hintText: this.onFontFamilyChanged,
LocaleKeys.settings_appearance_fontFamily_search.tr(), this.child,
autoFocus: false, this.popoverController,
debounceDuration: const Duration(milliseconds: 300), });
onChanged: (value) {
query.value = value; final String currentFontFamily;
}, final VoidCallback? onOpen;
), final VoidCallback? onClose;
final void Function(String fontFamily)? onFontFamilyChanged;
final Widget? child;
final PopoverController? popoverController;
@override
State<FontFamilyDropDown> createState() => _FontFamilyDropDownState();
}
class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
final List<String> availableFonts = GoogleFonts.asMap().keys.toList();
final ValueNotifier<String> query = ValueNotifier('');
@override
Widget build(BuildContext context) {
return ThemeValueDropDown(
popoverKey: ThemeFontFamilySetting.popoverKey,
popoverController: widget.popoverController,
currentValue: parseFontFamilyName(widget.currentFontFamily),
onClose: () {
query.value = '';
widget.onClose?.call();
},
child: widget.child,
popupBuilder: (_) {
widget.onOpen?.call();
return CustomScrollView(
shrinkWrap: true,
slivers: [
SliverPadding(
padding: const EdgeInsets.only(right: 8),
sliver: SliverToBoxAdapter(
child: FlowyTextField(
key: ThemeFontFamilySetting.textFieldKey,
hintText:
LocaleKeys.settings_appearance_fontFamily_search.tr(),
autoFocus: false,
debounceDuration: const Duration(milliseconds: 300),
onChanged: (value) {
query.value = value;
},
), ),
), ),
const SliverToBoxAdapter( ),
child: SizedBox(height: 4), const SliverToBoxAdapter(
), child: SizedBox(height: 4),
ValueListenableBuilder( ),
valueListenable: query, ValueListenableBuilder(
builder: (context, value, child) { valueListenable: query,
var displayed = availableFonts; builder: (context, value, child) {
if (value.isNotEmpty) { var displayed = availableFonts;
displayed = availableFonts if (value.isNotEmpty) {
.where( displayed = availableFonts
(font) => font .where(
.toLowerCase() (font) => font
.contains(value.toLowerCase().toString()), .toLowerCase()
) .contains(value.toLowerCase().toString()),
.sorted((a, b) => levenshtein(a, b)) )
.toList(); .sorted((a, b) => levenshtein(a, b))
} .toList();
return SliverFixedExtentList.builder( }
itemBuilder: (context, index) => _fontFamilyItemButton( return SliverFixedExtentList.builder(
context, itemBuilder: (context, index) => _fontFamilyItemButton(
GoogleFonts.getFont(displayed[index]), context,
), GoogleFonts.getFont(displayed[index]),
itemCount: displayed.length, ),
itemExtent: 32, itemCount: displayed.length,
); itemExtent: 32,
}, );
), },
], ),
), ],
), );
], },
); );
} }
@ -128,14 +167,17 @@ class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
) )
: null, : null,
onTap: () { onTap: () {
if (parseFontFamilyName(widget.currentFontFamily) != if (widget.onFontFamilyChanged != null) {
buttonFontFamily) { widget.onFontFamilyChanged!(style.fontFamily!);
context } else {
.read<AppearanceSettingsCubit>() final fontFamily = style.fontFamily!.parseFontFamilyName();
.setFontFamily(parseFontFamilyName(style.fontFamily!)); if (parseFontFamilyName(widget.currentFontFamily) !=
context buttonFontFamily) {
.read<DocumentAppearanceCubit>() context.read<AppearanceSettingsCubit>().setFontFamily(fontFamily);
.syncFontFamily(parseFontFamilyName(style.fontFamily!)); context
.read<DocumentAppearanceCubit>()
.syncFontFamily(fontFamily);
}
} }
}, },
), ),

View File

@ -31,6 +31,7 @@ class ThemeSettingEntryTemplateWidget extends StatelessWidget {
children: [ children: [
FlowyText.medium( FlowyText.medium(
label, label,
fontSize: 14,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
if (hint != null) if (hint != null)
@ -71,12 +72,16 @@ class ThemeValueDropDown extends StatefulWidget {
required this.popupBuilder, required this.popupBuilder,
this.popoverKey, this.popoverKey,
this.onClose, this.onClose,
this.child,
this.popoverController,
}); });
final String currentValue; final String currentValue;
final Key? popoverKey; final Key? popoverKey;
final Widget Function(BuildContext) popupBuilder; final Widget Function(BuildContext) popupBuilder;
final void Function()? onClose; final void Function()? onClose;
final Widget? child;
final PopoverController? popoverController;
@override @override
State<ThemeValueDropDown> createState() => _ThemeValueDropDownState(); State<ThemeValueDropDown> createState() => _ThemeValueDropDownState();
@ -87,6 +92,7 @@ class _ThemeValueDropDownState extends State<ThemeValueDropDown> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppFlowyPopover( return AppFlowyPopover(
key: widget.popoverKey, key: widget.popoverKey,
controller: widget.popoverController,
direction: PopoverDirection.bottomWithRightAligned, direction: PopoverDirection.bottomWithRightAligned,
popupBuilder: widget.popupBuilder, popupBuilder: widget.popupBuilder,
constraints: const BoxConstraints( constraints: const BoxConstraints(
@ -95,11 +101,12 @@ class _ThemeValueDropDownState extends State<ThemeValueDropDown> {
maxHeight: 400, maxHeight: 400,
), ),
onClose: widget.onClose, onClose: widget.onClose,
child: FlowyTextButton( child: widget.child ??
widget.currentValue, FlowyTextButton(
fontColor: Theme.of(context).colorScheme.onBackground, widget.currentValue,
fillColor: Colors.transparent, fontColor: Theme.of(context).colorScheme.onBackground,
), fillColor: Colors.transparent,
),
); );
} }
} }

View File

@ -19,23 +19,25 @@ class SettingsAppearanceView extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
BrightnessSetting(
currentThemeMode: state.themeMode,
),
ColorSchemeSetting( ColorSchemeSetting(
currentTheme: state.appTheme.themeName, currentTheme: state.appTheme.themeName,
bloc: context.read<DynamicPluginBloc>(), bloc: context.read<DynamicPluginBloc>(),
), ),
BrightnessSetting(
currentThemeMode: state.themeMode,
),
const Divider(),
ThemeFontFamilySetting( ThemeFontFamilySetting(
currentFontFamily: state.font, currentFontFamily: state.font,
), ),
// TODO: enablt them in version 0.3.3 const Divider(),
// LayoutDirectionSetting( LayoutDirectionSetting(
// currentLayoutDirection: state.layoutDirection, currentLayoutDirection: state.layoutDirection,
// ), ),
// TextDirectionSetting( TextDirectionSetting(
// currentTextDirection: state.textDirection, currentTextDirection: state.textDirection,
// ), ),
const Divider(),
CreateFileSettings(), CreateFileSettings(),
], ],
); );

View File

@ -54,11 +54,11 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "4a87ec4" ref: a0ff609
resolved-ref: "4a87ec4bd440344b8f51dd61ab84e2c68d4196d2" resolved-ref: a0ff609cb1ac53e5d167489f43452074860dd80e
url: "https://github.com/AppFlowy-IO/appflowy-editor.git" url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git source: git
version: "1.3.0" version: "1.4.0"
appflowy_popover: appflowy_popover:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -47,7 +47,7 @@ dependencies:
appflowy_editor: appflowy_editor:
git: git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: 4a87ec4 ref: a0ff609
appflowy_popover: appflowy_popover:
path: packages/appflowy_popover path: packages/appflowy_popover

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path d="M420-160v-520H200v-120h560v120H540v520H420Z" fill="white" />
</svg>

After

Width:  |  Height:  |  Size: 167 B

View File

@ -265,13 +265,13 @@
}, },
"layoutDirection": { "layoutDirection": {
"label": "Layout Direction", "label": "Layout Direction",
"hint": "To start aligning elements from left or right of the screen.", "hint": "Control the flow of content on your screen, from left to right or right to left.",
"ltr": "LTR", "ltr": "LTR",
"rtl": "RTL" "rtl": "RTL"
}, },
"textDirection": { "textDirection": {
"label": "Default text direction", "label": "Default text direction",
"hint": "Default text direction when the text direction is not set on the element.", "hint": "Specify whether text should start from left or right as the default.",
"ltr": "LTR", "ltr": "LTR",
"rtl": "RTL", "rtl": "RTL",
"auto": "AUTO", "auto": "AUTO",