|
@@ -1,8 +1,13 @@
|
|
|
import 'package:appflowy/generated/locale_keys.g.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:easy_localization/easy_localization.dart';
|
|
|
import 'package:flowy_infra/image.dart';
|
|
|
+import 'package:flowy_infra/plugins/bloc/dynamic_plugin_bloc.dart';
|
|
|
+import 'package:flowy_infra/plugins/bloc/dynamic_plugin_event.dart';
|
|
|
+import 'package:flowy_infra/plugins/bloc/dynamic_plugin_state.dart';
|
|
|
import 'package:flowy_infra/theme.dart';
|
|
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|
|
import 'package:flutter/material.dart';
|
|
@@ -14,28 +19,37 @@ class SettingsAppearanceView extends StatelessWidget {
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
return SingleChildScrollView(
|
|
|
- child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
|
|
|
- builder: (context, state) {
|
|
|
- return Column(
|
|
|
- crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
- children: [
|
|
|
- ThemeModeSetting(currentThemeMode: state.themeMode),
|
|
|
- ThemeSetting(currentTheme: state.appTheme.themeName),
|
|
|
- ],
|
|
|
- );
|
|
|
- },
|
|
|
+ child: BlocProvider<DynamicPluginBloc>(
|
|
|
+ create: (_) => DynamicPluginBloc(),
|
|
|
+ child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
|
|
|
+ builder: (context, state) {
|
|
|
+ return Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ BrightnessSetting(currentThemeMode: state.themeMode),
|
|
|
+ ColorSchemeSetting(
|
|
|
+ currentTheme: state.appTheme.themeName,
|
|
|
+ bloc: context.read<DynamicPluginBloc>(),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-class ThemeSetting extends StatelessWidget {
|
|
|
- final String currentTheme;
|
|
|
- const ThemeSetting({
|
|
|
+class ColorSchemeSetting extends StatelessWidget {
|
|
|
+ const ColorSchemeSetting({
|
|
|
super.key,
|
|
|
required this.currentTheme,
|
|
|
+ required this.bloc,
|
|
|
});
|
|
|
|
|
|
+ final String currentTheme;
|
|
|
+ final DynamicPluginBloc bloc;
|
|
|
+
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
return Row(
|
|
@@ -46,52 +60,152 @@ class ThemeSetting extends StatelessWidget {
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
),
|
|
|
),
|
|
|
- AppFlowyPopover(
|
|
|
- direction: PopoverDirection.bottomWithRightAligned,
|
|
|
- child: FlowyTextButton(
|
|
|
- currentTheme,
|
|
|
- fontColor: Theme.of(context).colorScheme.onBackground,
|
|
|
- fillColor: Colors.transparent,
|
|
|
- onPressed: () {},
|
|
|
+ ThemeUploadOverlayButton(bloc: bloc),
|
|
|
+ const SizedBox(width: 4),
|
|
|
+ ThemeSelectionPopover(currentTheme: currentTheme, bloc: bloc),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class ThemeUploadOverlayButton extends StatelessWidget {
|
|
|
+ const ThemeUploadOverlayButton({super.key, required this.bloc});
|
|
|
+
|
|
|
+ final DynamicPluginBloc bloc;
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return FlowyIconButton(
|
|
|
+ width: 24,
|
|
|
+ icon: const FlowySvg(name: 'folder'),
|
|
|
+ iconColorOnHover: Theme.of(context).colorScheme.onPrimary,
|
|
|
+ onPressed: () => Dialogs.show(
|
|
|
+ context,
|
|
|
+ child: BlocProvider<DynamicPluginBloc>.value(
|
|
|
+ value: bloc,
|
|
|
+ child: const FlowyDialog(
|
|
|
+ constraints: BoxConstraints(maxHeight: 300),
|
|
|
+ child: ThemeUploadWidget(),
|
|
|
),
|
|
|
- popupBuilder: (BuildContext context) {
|
|
|
- return IntrinsicWidth(
|
|
|
- child: Column(
|
|
|
- mainAxisSize: MainAxisSize.min,
|
|
|
- children: [
|
|
|
- _themeItemButton(context, BuiltInTheme.defaultTheme),
|
|
|
- _themeItemButton(context, BuiltInTheme.dandelion),
|
|
|
- _themeItemButton(context, BuiltInTheme.lavender),
|
|
|
- ],
|
|
|
- ),
|
|
|
- );
|
|
|
- },
|
|
|
),
|
|
|
- ],
|
|
|
+ ).then((value) {
|
|
|
+ if (value == null) return;
|
|
|
+ ScaffoldMessenger.of(context).showSnackBar(
|
|
|
+ SnackBar(
|
|
|
+ content: FlowyText.medium(
|
|
|
+ color: Theme.of(context).colorScheme.onPrimary,
|
|
|
+ LocaleKeys.settings_appearance_themeUpload_uploadSuccess.tr(),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }),
|
|
|
);
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- Widget _themeItemButton(BuildContext context, String theme) {
|
|
|
+class ThemeSelectionPopover extends StatelessWidget {
|
|
|
+ const ThemeSelectionPopover({
|
|
|
+ super.key,
|
|
|
+ required this.currentTheme,
|
|
|
+ required this.bloc,
|
|
|
+ });
|
|
|
+
|
|
|
+ final String currentTheme;
|
|
|
+ final DynamicPluginBloc bloc;
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return AppFlowyPopover(
|
|
|
+ direction: PopoverDirection.bottomWithRightAligned,
|
|
|
+ child: FlowyTextButton(
|
|
|
+ currentTheme,
|
|
|
+ fontColor: Theme.of(context).colorScheme.onBackground,
|
|
|
+ fillColor: Colors.transparent,
|
|
|
+ onPressed: () {},
|
|
|
+ ),
|
|
|
+ popupBuilder: (BuildContext context) {
|
|
|
+ return IntrinsicWidth(
|
|
|
+ child: BlocBuilder<DynamicPluginBloc, DynamicPluginState>(
|
|
|
+ bloc: bloc..add(DynamicPluginEvent.load()),
|
|
|
+ buildWhen: (previous, current) => current is Ready,
|
|
|
+ builder: (context, state) {
|
|
|
+ return state.when(
|
|
|
+ uninitialized: () => const SizedBox.shrink(),
|
|
|
+ processing: () => const SizedBox.shrink(),
|
|
|
+ compilationFailure: (message) => const SizedBox.shrink(),
|
|
|
+ deletionFailure: (message) => const SizedBox.shrink(),
|
|
|
+ deletionSuccess: () => const SizedBox.shrink(),
|
|
|
+ compilationSuccess: () => const SizedBox.shrink(),
|
|
|
+ ready: (plugins) => Column(
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ children: [
|
|
|
+ ...AppTheme.builtins
|
|
|
+ .map(
|
|
|
+ (theme) => _themeItemButton(context, theme.themeName),
|
|
|
+ )
|
|
|
+ .toList(),
|
|
|
+ if (plugins.isNotEmpty) ...[
|
|
|
+ const Divider(),
|
|
|
+ ...plugins
|
|
|
+ .map((plugin) => plugin.theme)
|
|
|
+ .whereType<AppTheme>()
|
|
|
+ .map(
|
|
|
+ (theme) => _themeItemButton(
|
|
|
+ context,
|
|
|
+ theme.themeName,
|
|
|
+ false,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ .toList()
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _themeItemButton(
|
|
|
+ BuildContext context,
|
|
|
+ String theme, [
|
|
|
+ bool isBuiltin = true,
|
|
|
+ ]) {
|
|
|
return SizedBox(
|
|
|
height: 32,
|
|
|
- child: FlowyButton(
|
|
|
- text: FlowyText.medium(theme),
|
|
|
- rightIcon: currentTheme == theme
|
|
|
- ? const FlowySvg(name: 'grid/checkmark')
|
|
|
- : null,
|
|
|
- onTap: () {
|
|
|
- if (currentTheme != theme) {
|
|
|
- context.read<AppearanceSettingsCubit>().setTheme(theme);
|
|
|
- }
|
|
|
- },
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: FlowyButton(
|
|
|
+ text: FlowyText.medium(theme),
|
|
|
+ rightIcon: currentTheme == theme
|
|
|
+ ? const FlowySvg(name: 'grid/checkmark')
|
|
|
+ : null,
|
|
|
+ onTap: () {
|
|
|
+ if (currentTheme != theme) {
|
|
|
+ context.read<AppearanceSettingsCubit>().setTheme(theme);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ if (!isBuiltin)
|
|
|
+ FlowyIconButton(
|
|
|
+ icon: const FlowySvg(name: 'home/close'),
|
|
|
+ width: 20,
|
|
|
+ onPressed: () =>
|
|
|
+ bloc.add(DynamicPluginEvent.removePlugin(name: theme)),
|
|
|
+ )
|
|
|
+ ],
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-class ThemeModeSetting extends StatelessWidget {
|
|
|
+class BrightnessSetting extends StatelessWidget {
|
|
|
final ThemeMode currentThemeMode;
|
|
|
- const ThemeModeSetting({required this.currentThemeMode, super.key});
|
|
|
+ const BrightnessSetting({required this.currentThemeMode, super.key});
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|