diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/color_scheme.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/color_scheme.dart index 88b5ccb676..bf91f92097 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/color_scheme.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/color_scheme.dart @@ -157,7 +157,8 @@ class ColorSchemeUploadPopover extends StatelessWidget { }, ), ), - if (!isBuiltin) + // when the custom theme is not the current theme, show the remove button + if (!isBuiltin && currentTheme != theme) FlowyIconButton( icon: const FlowySvg( FlowySvgs.close_s, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload.dart new file mode 100644 index 0000000000..07a9b19273 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload.dart @@ -0,0 +1,8 @@ +export 'theme_confirm_delete_dialog.dart'; +export 'theme_upload_button.dart'; +export 'theme_upload_learn_more_button.dart'; +export 'theme_upload_decoration.dart'; +export 'theme_upload_failure_widget.dart'; +export 'theme_upload_loading_widget.dart'; +export 'theme_upload_view.dart'; +export 'upload_new_theme_widget.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_failure_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_failure_widget.dart index 68b8fe6425..ca67a01f65 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_failure_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_failure_widget.dart @@ -1,14 +1,12 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:easy_localization/easy_localization.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; -import 'theme_upload_button.dart'; -import 'theme_upload_view.dart'; - class ThemeUploadFailureWidget extends StatelessWidget { - const ThemeUploadFailureWidget({super.key}); + const ThemeUploadFailureWidget({super.key, required this.errorMessage}); + + final String errorMessage; @override Widget build(BuildContext context) { @@ -23,17 +21,21 @@ class ThemeUploadFailureWidget extends StatelessWidget { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ + const Spacer(), FlowySvg( FlowySvgs.close_m, size: ThemeUploadWidget.iconSize, color: Theme.of(context).colorScheme.onBackground, ), FlowyText.medium( - LocaleKeys.settings_appearance_themeUpload_failure.tr(), + errorMessage, overflow: TextOverflow.ellipsis, ), ThemeUploadWidget.elementSpacer, + const ThemeUploadLearnMoreButton(), + ThemeUploadWidget.elementSpacer, ThemeUploadButton(color: Theme.of(context).colorScheme.error), + ThemeUploadWidget.elementSpacer, ], ), ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart new file mode 100644 index 0000000000..5c2d8a9fb5 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart @@ -0,0 +1,58 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; +import 'package:flowy_infra_ui/widget/error_page.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class ThemeUploadLearnMoreButton extends StatelessWidget { + const ThemeUploadLearnMoreButton({super.key}); + + static const learnMoreURL = + 'https://appflowy.gitbook.io/docs/essential-documentation/themes'; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: ThemeUploadWidget.buttonSize.height, + child: IntrinsicWidth( + child: SecondaryButton( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: FlowyText.medium( + fontSize: ThemeUploadWidget.buttonFontSize, + LocaleKeys.document_plugins_autoGeneratorLearnMore.tr(), + ), + ), + onPressed: () async { + final uri = Uri.parse(learnMoreURL); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } else { + if (context.mounted) { + Dialogs.show( + context, + child: FlowyDialog( + child: FlowyErrorPage.message( + LocaleKeys + .settings_appearance_themeUpload_urlUploadFailure + .tr() + .replaceAll( + '{}', + uri.toString(), + ), + howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(), + ), + ), + ); + } + } + }, + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart index 4fea7a1081..f9705843d1 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart @@ -13,7 +13,7 @@ class ThemeUploadWidget extends StatefulWidget { static const double borderRadius = 8; static const double buttonFontSize = 14; - static const Size buttonSize = Size(72, 28); + static const Size buttonSize = Size(100, 32); static const EdgeInsets padding = EdgeInsets.all(12.0); static const Size iconSize = Size.square(48); static const Widget elementSpacer = SizedBox(height: 12); @@ -41,9 +41,10 @@ class _ThemeUploadWidgetState extends State { key: Key('upload_theme_loading_widget'), ); }, - compilationFailure: () { - child = const ThemeUploadFailureWidget( - key: Key('upload_theme_failure_widget'), + compilationFailure: (errorMessage) { + child = ThemeUploadFailureWidget( + key: const Key('upload_theme_failure_widget'), + errorMessage: errorMessage, ); }, compilationSuccess: () { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/upload_new_theme_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/upload_new_theme_widget.dart index 2d62c23830..6605312028 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/upload_new_theme_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/upload_new_theme_widget.dart @@ -1,21 +1,13 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_button.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import 'theme_upload_view.dart'; class UploadNewThemeWidget extends StatelessWidget { const UploadNewThemeWidget({super.key}); - static const learnMoreRedirect = - 'https://appflowy.gitbook.io/docs/essential-documentation/themes'; - @override Widget build(BuildContext context) { return Container( @@ -28,6 +20,7 @@ class UploadNewThemeWidget extends StatelessWidget { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ + const Spacer(), FlowySvg( FlowySvgs.folder_m, size: ThemeUploadWidget.iconSize, @@ -38,51 +31,12 @@ class UploadNewThemeWidget extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ThemeUploadWidget.elementSpacer, - SizedBox( - height: ThemeUploadWidget.buttonSize.height, - child: IntrinsicWidth( - child: FlowyButton( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Theme.of(context).colorScheme.onBackground, - ), - hoverColor: Theme.of(context).colorScheme.onBackground, - text: FlowyText.medium( - fontSize: ThemeUploadWidget.buttonFontSize, - color: Theme.of(context).colorScheme.onPrimary, - LocaleKeys.document_plugins_autoGeneratorLearnMore.tr(), - ), - onTap: () async { - final uri = Uri.parse(learnMoreRedirect); - if (await canLaunchUrl(uri)) { - await launchUrl(uri); - } else { - if (context.mounted) { - Dialogs.show( - context, - child: FlowyDialog( - child: FlowyErrorPage.message( - LocaleKeys - .settings_appearance_themeUpload_urlUploadFailure - .tr() - .replaceAll( - '{}', - uri.toString(), - ), - howToFix: - LocaleKeys.errorDialog_howToFixFallback.tr(), - ), - ), - ); - } - } - }, - ), - ), - ), + const ThemeUploadLearnMoreButton(), + ThemeUploadWidget.elementSpacer, const Divider(), ThemeUploadWidget.elementSpacer, const ThemeUploadButton(), + ThemeUploadWidget.elementSpacer, ], ), ); diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/default_colorscheme.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/default_colorscheme.dart index ac8b04100d..706d7cc3ad 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/default_colorscheme.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/default_colorscheme.dart @@ -64,14 +64,14 @@ class DefaultColorScheme extends FlowyColorScheme { onPrimary: _white, hoverBG1: _lightBg2, hoverBG2: _lightHover, + hoverBG3: _lightShader6, hoverFG: _lightShader1, questionBubbleBG: _lightSelector, - hoverBG3: _lightShader6, progressBarBGColor: _lightTint9, toolbarColor: _lightShader1, toggleButtonBGColor: _lightShader5, calendarWeekendBGColor: const Color(0xFFFBFBFC), - gridRowCountColor: const Color(0xff000000), + gridRowCountColor: _lightShader1, ); const DefaultColorScheme.dark() @@ -122,7 +122,7 @@ class DefaultColorScheme extends FlowyColorScheme { progressBarBGColor: _darkShader3, toolbarColor: _darkInput, toggleButtonBGColor: _darkShader1, - calendarWeekendBGColor: const Color(0xff121212), - gridRowCountColor: _darkMain1, + calendarWeekendBGColor: _darkShader1, + gridRowCountColor: _darkShader5, ); } diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/bloc/dynamic_plugin_bloc.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/bloc/dynamic_plugin_bloc.dart index 5639757ab8..1aaa75ef57 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/bloc/dynamic_plugin_bloc.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/bloc/dynamic_plugin_bloc.dart @@ -37,8 +37,9 @@ class DynamicPluginBloc extends Bloc { return; } await FlowyPluginService.instance.addPlugin(plugin); - } on PluginCompilationException { - return emit(const DynamicPluginState.compilationFailure()); + } on PluginCompilationException catch (exception) { + return emit(DynamicPluginState.compilationFailure( + errorMessage: exception.message)); } emit(const DynamicPluginState.compilationSuccess()); diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/bloc/dynamic_plugin_state.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/bloc/dynamic_plugin_state.dart index 397ddff8d3..cc34e8ebac 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/bloc/dynamic_plugin_state.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/bloc/dynamic_plugin_state.dart @@ -11,7 +11,8 @@ class DynamicPluginState with _$DynamicPluginState { required Iterable plugins, }) = Ready; const factory DynamicPluginState.processing() = _Processing; - const factory DynamicPluginState.compilationFailure() = _CompilationFailure; + const factory DynamicPluginState.compilationFailure( + {required String errorMessage}) = _CompilationFailure; const factory DynamicPluginState.deletionFailure({ required String path, }) = _DeletionFailure; diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/service/models/flowy_dynamic_plugin.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/service/models/flowy_dynamic_plugin.dart index 3b6ed43cba..93e972eeba 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/service/models/flowy_dynamic_plugin.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/plugins/service/models/flowy_dynamic_plugin.dart @@ -74,21 +74,21 @@ class FlowyDynamicPlugin { /// compilation error will be thrown during the construction of this object. Future encode() async { final fs = MemoryFileSystem(); - final result = fs.directory(_fsPluginName)..createSync(); + final directory = fs.directory(_fsPluginName)..createSync(); - final lightPath = p.join(_fsPluginName, '$name.$lightExtension'); - result.childFile(lightPath).createSync(); - result - .childFile(lightPath) + final lightThemeFileName = '$name.$lightExtension'; + directory.childFile(lightThemeFileName).createSync(); + directory + .childFile(lightThemeFileName) .writeAsStringSync(jsonEncode(theme!.lightTheme.toJson())); - final darkPath = p.join(_fsPluginName, '$name.$darkExtension'); - result.childFile(darkPath).createSync(); - result - .childFile(p.join(_fsPluginName, '$name.$darkExtension')) + final darkThemeFileName = '$name.$darkExtension'; + directory.childFile(darkThemeFileName).createSync(); + directory + .childFile(darkThemeFileName) .writeAsStringSync(jsonEncode(theme!.darkTheme.toJson())); - return result; + return directory; } /// Theme plugins should have the following format. @@ -119,13 +119,32 @@ class FlowyDynamicPlugin { event is File && p.basename(event.path).contains(darkExtension)) .first as File; + late final FlowyColorScheme lightTheme; + late final FlowyColorScheme darkTheme; + + try { + lightTheme = FlowyColorScheme.fromJson( + await jsonDecode(await light.readAsString())); + } catch (e) { + throw PluginCompilationException( + 'The light theme json file is not valid.', + ); + } + + try { + darkTheme = FlowyColorScheme.fromJson( + await jsonDecode(await dark.readAsString())); + } catch (e) { + throw PluginCompilationException( + 'The dark theme json file is not valid.', + ); + } + final theme = AppTheme( themeName: name, builtIn: false, - lightTheme: - FlowyColorScheme.fromJson(jsonDecode(await light.readAsString())), - darkTheme: - FlowyColorScheme.fromJson(jsonDecode(await dark.readAsString())), + lightTheme: lightTheme, + darkTheme: darkTheme, ); return FlowyDynamicPlugin._( diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart index 886592b7dc..e1014a226b 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart @@ -76,8 +76,8 @@ class SecondaryButton extends StatelessWidget { minWidth: size.width, minHeight: size.height, contentPadding: EdgeInsets.zero, - bgColor: Theme.of(context).colorScheme.surface, - outlineColor: Theme.of(context).colorScheme.primary, + bgColor: Colors.transparent, + outlineColor: Theme.of(context).colorScheme.onBackground, borderRadius: mode.borderRadius, onPressed: onPressed, child: child, diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 55473e881f..95d322d953 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -356,7 +356,6 @@ "button": "Upload", "uploadTheme": "Upload theme", "description": "Upload your own AppFlowy theme using the button below.", - "failure": "The theme that was uploaded had an invalid format.", "loading": "Please wait while we validate and upload your theme...", "uploadSuccess": "Your theme was uploaded successfully", "deletionFailure": "Failed to delete the theme. Try to delete it manually.",