|
@@ -1,8 +1,10 @@
|
|
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
|
|
+import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
|
|
import 'package:appflowy/workspace/application/appearance.dart';
|
|
|
import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart';
|
|
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
|
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
|
|
+import 'package:collection/collection.dart';
|
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
|
import 'package:flowy_infra/image.dart';
|
|
|
import 'package:flowy_infra/plugins/bloc/dynamic_plugin_bloc.dart';
|
|
@@ -12,6 +14,9 @@ import 'package:flowy_infra/theme.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';
|
|
|
+
|
|
|
+import 'levenshtein.dart';
|
|
|
|
|
|
class SettingsAppearanceView extends StatelessWidget {
|
|
|
const SettingsAppearanceView({Key? key}) : super(key: key);
|
|
@@ -31,6 +36,9 @@ class SettingsAppearanceView extends StatelessWidget {
|
|
|
currentTheme: state.appTheme.themeName,
|
|
|
bloc: context.read<DynamicPluginBloc>(),
|
|
|
),
|
|
|
+ ThemeFontFamilySetting(
|
|
|
+ currentFontFamily: state.font,
|
|
|
+ ),
|
|
|
],
|
|
|
);
|
|
|
},
|
|
@@ -209,36 +217,17 @@ class BrightnessSetting extends StatelessWidget {
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
- return Row(
|
|
|
- children: [
|
|
|
- Expanded(
|
|
|
- child: FlowyText.medium(
|
|
|
- LocaleKeys.settings_appearance_themeMode_label.tr(),
|
|
|
- overflow: TextOverflow.ellipsis,
|
|
|
- ),
|
|
|
- ),
|
|
|
- AppFlowyPopover(
|
|
|
- direction: PopoverDirection.bottomWithRightAligned,
|
|
|
- child: FlowyTextButton(
|
|
|
- _themeModeLabelText(currentThemeMode),
|
|
|
- fontColor: Theme.of(context).colorScheme.onBackground,
|
|
|
- fillColor: Colors.transparent,
|
|
|
- onPressed: () {},
|
|
|
- ),
|
|
|
- popupBuilder: (BuildContext context) {
|
|
|
- return IntrinsicWidth(
|
|
|
- child: Column(
|
|
|
- mainAxisSize: MainAxisSize.min,
|
|
|
- children: [
|
|
|
- _themeModeItemButton(context, ThemeMode.light),
|
|
|
- _themeModeItemButton(context, ThemeMode.dark),
|
|
|
- _themeModeItemButton(context, ThemeMode.system),
|
|
|
- ],
|
|
|
- ),
|
|
|
- );
|
|
|
- },
|
|
|
- ),
|
|
|
- ],
|
|
|
+ return ThemeSettingDropDown(
|
|
|
+ label: LocaleKeys.settings_appearance_themeMode_label.tr(),
|
|
|
+ currentValue: _themeModeLabelText(currentThemeMode),
|
|
|
+ popupBuilder: (_) => Column(
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ children: [
|
|
|
+ _themeModeItemButton(context, ThemeMode.light),
|
|
|
+ _themeModeItemButton(context, ThemeMode.dark),
|
|
|
+ _themeModeItemButton(context, ThemeMode.system),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -272,3 +261,161 @@ class BrightnessSetting extends StatelessWidget {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+class ThemeFontFamilySetting extends StatefulWidget {
|
|
|
+ const ThemeFontFamilySetting({
|
|
|
+ super.key,
|
|
|
+ required this.currentFontFamily,
|
|
|
+ });
|
|
|
+
|
|
|
+ final String currentFontFamily;
|
|
|
+
|
|
|
+ @override
|
|
|
+ State<ThemeFontFamilySetting> createState() => _ThemeFontFamilySettingState();
|
|
|
+}
|
|
|
+
|
|
|
+class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
|
|
|
+ final List<String> availableFonts = GoogleFonts.asMap().keys.toList();
|
|
|
+ final ValueNotifier<String> query = ValueNotifier('');
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return ThemeSettingDropDown(
|
|
|
+ label: LocaleKeys.settings_appearance_fontFamily_label.tr(),
|
|
|
+ currentValue: parseFontFamilyName(widget.currentFontFamily),
|
|
|
+ onClose: () {
|
|
|
+ query.value = '';
|
|
|
+ },
|
|
|
+ popupBuilder: (_) => CustomScrollView(
|
|
|
+ shrinkWrap: true,
|
|
|
+ slivers: [
|
|
|
+ SliverPadding(
|
|
|
+ padding: const EdgeInsets.only(right: 8),
|
|
|
+ sliver: SliverToBoxAdapter(
|
|
|
+ child: FlowyTextField(
|
|
|
+ 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,
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ String parseFontFamilyName(String fontFamilyName) {
|
|
|
+ final camelCase = RegExp('(?<=[a-z])[A-Z]');
|
|
|
+ return fontFamilyName
|
|
|
+ .replaceAll('_regular', '')
|
|
|
+ .replaceAllMapped(camelCase, (m) => ' ${m.group(0)}');
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _fontFamilyItemButton(BuildContext context, TextStyle style) {
|
|
|
+ final buttonFontFamily = parseFontFamilyName(style.fontFamily!);
|
|
|
+ return SizedBox(
|
|
|
+ key: UniqueKey(),
|
|
|
+ height: 32,
|
|
|
+ child: FlowyButton(
|
|
|
+ text: FlowyText.medium(
|
|
|
+ parseFontFamilyName(style.fontFamily!),
|
|
|
+ fontFamily: style.fontFamily!,
|
|
|
+ ),
|
|
|
+ rightIcon:
|
|
|
+ buttonFontFamily == parseFontFamilyName(widget.currentFontFamily)
|
|
|
+ ? const FlowySvg(name: 'grid/checkmark')
|
|
|
+ : null,
|
|
|
+ onTap: () {
|
|
|
+ if (parseFontFamilyName(widget.currentFontFamily) !=
|
|
|
+ buttonFontFamily) {
|
|
|
+ context
|
|
|
+ .read<AppearanceSettingsCubit>()
|
|
|
+ .setFontFamily(parseFontFamilyName(style.fontFamily!));
|
|
|
+ context
|
|
|
+ .read<DocumentAppearanceCubit>()
|
|
|
+ .syncFontFamily(parseFontFamilyName(style.fontFamily!));
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class ThemeSettingDropDown extends StatefulWidget {
|
|
|
+ const ThemeSettingDropDown({
|
|
|
+ super.key,
|
|
|
+ required this.label,
|
|
|
+ required this.currentValue,
|
|
|
+ required this.popupBuilder,
|
|
|
+ this.onClose,
|
|
|
+ });
|
|
|
+
|
|
|
+ final String label;
|
|
|
+ final String currentValue;
|
|
|
+ final Widget Function(BuildContext) popupBuilder;
|
|
|
+ final void Function()? onClose;
|
|
|
+
|
|
|
+ @override
|
|
|
+ State<ThemeSettingDropDown> createState() => _ThemeSettingDropDownState();
|
|
|
+}
|
|
|
+
|
|
|
+class _ThemeSettingDropDownState extends State<ThemeSettingDropDown> {
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: FlowyText.medium(
|
|
|
+ widget.label,
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ AppFlowyPopover(
|
|
|
+ direction: PopoverDirection.bottomWithRightAligned,
|
|
|
+ popupBuilder: widget.popupBuilder,
|
|
|
+ constraints: const BoxConstraints(
|
|
|
+ minWidth: 80,
|
|
|
+ maxWidth: 160,
|
|
|
+ maxHeight: 400,
|
|
|
+ ),
|
|
|
+ onClose: widget.onClose,
|
|
|
+ child: FlowyTextButton(
|
|
|
+ widget.currentValue,
|
|
|
+ fontColor: Theme.of(context).colorScheme.onBackground,
|
|
|
+ fillColor: Colors.transparent,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|