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.appearance);
expect(find.textContaining(DefaultAppearanceSettings.kDefaultFontFamily),
expect(
find.textContaining(DefaultAppearanceSettings.kDefaultFontFamily),
findsOneWidget,
);
});

View File

@ -12,8 +12,11 @@ class MyMockClient extends Mock implements http.Client {
final requestType = request.method;
final requestUri = request.url;
if (requestType == 'POST' && requestUri == OpenAIRequestType.textCompletion.uri) {
final responseHeaders = <String, String>{'content-type': 'text/event-stream'};
if (requestType == 'POST' &&
requestUri == OpenAIRequestType.textCompletion.uri) {
final responseHeaders = <String, String>{
'content-type': 'text/event-stream'
};
final responseBody = Stream.fromIterable([
utf8.encode(
'{ "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 = '';
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();
final data = chunk.trim().split('data: ');
if (data[0] != '[DONE]') {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -190,30 +190,49 @@ class EmojiPickerState extends State<EmojiPicker> {
categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap));
}
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(
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'),)
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(
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
Future<List<Emoji>> _getAvailableEmojis(Map<String, String> map,
{required String title,}) async {
Future<List<Emoji>> _getAvailableEmojis(
Map<String, String> map, {
required String title,
}) async {
Map<String, String>? newMap;
// Get Emojis cached locally if available
@ -236,15 +255,18 @@ class EmojiPickerState extends State<EmojiPicker> {
// Check if emoji is available on current platform
Future<Map<String, String>?> _getPlatformAvailableEmoji(
Map<String, String> emoji,) async {
Map<String, String> emoji,
) async {
if (Platform.isAndroid) {
Map<String, String>? filtered = {};
const delimiter = '|';
try {
final entries = emoji.values.join(delimiter);
final keys = emoji.keys.join(delimiter);
final result = (await platform.invokeMethod<String>('checkAvailability',
{'emojiKeys': keys, 'emojiEntries': entries},)) as String;
final result = (await platform.invokeMethod<String>(
'checkAvailability',
{'emojiKeys': keys, 'emojiEntries': entries},
)) as String;
final resultKeys = result.split(delimiter);
for (var i = 0; i < resultKeys.length; 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
Future<void> _cacheFilteredEmojis(
String title, Map<String, String> emojis,) async {
String title,
Map<String, String> emojis,
) async {
final prefs = await SharedPreferences.getInstance();
final emojiJson = jsonEncode(emojis);
prefs.setString(title, emojiJson);
@ -305,7 +329,9 @@ class EmojiPickerState extends State<EmojiPicker> {
recentEmoji.sort((a, b) => b.counter - a.counter);
// Limit entries to recentsLimit
recentEmoji = recentEmoji.sublist(
0, min(widget.config.recentsLimit, recentEmoji.length),);
0,
min(widget.config.recentsLimit, recentEmoji.length),
);
// save locally
prefs.setString('recent', jsonEncode(recentEmoji));
}

View File

@ -1,4 +1,3 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/theme_extension.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 {
MathEquationBlockComponentBuilder({
this.configuration = const BlockComponentConfiguration(),
super.configuration,
});
@override
final BlockComponentConfiguration configuration;
@override
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
final node = blockComponentContext.node;

View File

@ -32,12 +32,9 @@ Node outlineBlockNode() {
class OutlineBlockComponentBuilder extends BlockComponentBuilder {
OutlineBlockComponentBuilder({
this.configuration = const BlockComponentConfiguration(),
super.configuration,
});
@override
final BlockComponentConfiguration configuration;
@override
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
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 'emoji_picker/emoji_menu_item.dart';
export 'extensions/flowy_tint_extension.dart';
export 'font/customize_font_toolbar_item.dart';
export 'header/cover_editor_bloc.dart';
export 'header/custom_cover_picker.dart';
export 'header/document_header_node_widget.dart';

View File

@ -55,13 +55,10 @@ SelectionMenuItem toggleListBlockItem = SelectionMenuItem.node(
class ToggleListBlockComponentBuilder extends BlockComponentBuilder {
ToggleListBlockComponentBuilder({
this.configuration = const BlockComponentConfiguration(),
super.configuration,
this.padding = const EdgeInsets.all(0),
});
@override
final BlockComponentConfiguration configuration;
final EdgeInsets padding;
@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/mention/mention_block.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:collection/collection.dart';
import 'package:flutter/material.dart';
@ -193,6 +194,15 @@ class EditorStyleCustomizer {
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
final mention = attributes[MentionBlockKeys.mention] as Map?;
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 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) {
// Poppins and SF Mono are not well supported in some languages, so use the
// 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/locale_keys.g.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_defaults.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -29,9 +31,6 @@ class ThemeFontFamilySetting extends StatefulWidget {
}
class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
final List<String> availableFonts = GoogleFonts.asMap().keys.toList();
final ValueNotifier<String> query = ValueNotifier('');
@override
Widget build(BuildContext context) {
return ThemeSettingEntryTemplateWidget(
@ -44,61 +43,101 @@ class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
.syncFontFamily(DefaultAppearanceSettings.kDefaultFontFamily);
},
trailing: [
ThemeValueDropDown(
popoverKey: ThemeFontFamilySetting.popoverKey,
currentValue: parseFontFamilyName(widget.currentFontFamily),
onClose: () {
query.value = '';
},
popupBuilder: (_) => 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;
},
),
FontFamilyDropDown(
currentFontFamily: widget.currentFontFamily,
)
],
);
}
}
class FontFamilyDropDown extends StatefulWidget {
const FontFamilyDropDown({
super.key,
required this.currentFontFamily,
this.onOpen,
this.onClose,
this.onFontFamilyChanged,
this.child,
this.popoverController,
});
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),
),
ValueListenableBuilder(
valueListenable: query,
builder: (context, value, child) {
var displayed = availableFonts;
if (value.isNotEmpty) {
displayed = availableFonts
.where(
(font) => font
.toLowerCase()
.contains(value.toLowerCase().toString()),
)
.sorted((a, b) => levenshtein(a, b))
.toList();
}
return SliverFixedExtentList.builder(
itemBuilder: (context, index) => _fontFamilyItemButton(
context,
GoogleFonts.getFont(displayed[index]),
),
itemCount: displayed.length,
itemExtent: 32,
);
},
),
],
),
),
],
),
const SliverToBoxAdapter(
child: SizedBox(height: 4),
),
ValueListenableBuilder(
valueListenable: query,
builder: (context, value, child) {
var displayed = availableFonts;
if (value.isNotEmpty) {
displayed = availableFonts
.where(
(font) => font
.toLowerCase()
.contains(value.toLowerCase().toString()),
)
.sorted((a, b) => levenshtein(a, b))
.toList();
}
return SliverFixedExtentList.builder(
itemBuilder: (context, index) => _fontFamilyItemButton(
context,
GoogleFonts.getFont(displayed[index]),
),
itemCount: displayed.length,
itemExtent: 32,
);
},
),
],
);
},
);
}
@ -128,14 +167,17 @@ class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
)
: null,
onTap: () {
if (parseFontFamilyName(widget.currentFontFamily) !=
buttonFontFamily) {
context
.read<AppearanceSettingsCubit>()
.setFontFamily(parseFontFamilyName(style.fontFamily!));
context
.read<DocumentAppearanceCubit>()
.syncFontFamily(parseFontFamilyName(style.fontFamily!));
if (widget.onFontFamilyChanged != null) {
widget.onFontFamilyChanged!(style.fontFamily!);
} else {
final fontFamily = style.fontFamily!.parseFontFamilyName();
if (parseFontFamilyName(widget.currentFontFamily) !=
buttonFontFamily) {
context.read<AppearanceSettingsCubit>().setFontFamily(fontFamily);
context
.read<DocumentAppearanceCubit>()
.syncFontFamily(fontFamily);
}
}
},
),

View File

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

View File

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

View File

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

View File

@ -47,7 +47,7 @@ dependencies:
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: 4a87ec4
ref: a0ff609
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": {
"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",
"rtl": "RTL"
},
"textDirection": {
"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",
"rtl": "RTL",
"auto": "AUTO",