fix: upload custom theme issue (#4317)

* fix: FlowyDynamicPlugin encode issue

* chore: improve the default theme color

* fix: learn more button text invisiable

* fix: fix read the wrong theme mode file

* fix: prevent current custom theme being deleted

* chore: improve theme upload UI and error prompts

* chore: delete unnecessary LocaleKeys
This commit is contained in:
Yijing Huang
2024-01-10 07:21:05 -06:00
committed by GitHub
parent 239bf2fa70
commit b1cc4e485b
12 changed files with 131 additions and 87 deletions

View File

@ -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( FlowyIconButton(
icon: const FlowySvg( icon: const FlowySvg(
FlowySvgs.close_s, FlowySvgs.close_s,

View File

@ -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';

View File

@ -1,14 +1,12 @@
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/workspace/presentation/settings/widgets/theme_upload/theme_upload.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'theme_upload_button.dart';
import 'theme_upload_view.dart';
class ThemeUploadFailureWidget extends StatelessWidget { class ThemeUploadFailureWidget extends StatelessWidget {
const ThemeUploadFailureWidget({super.key}); const ThemeUploadFailureWidget({super.key, required this.errorMessage});
final String errorMessage;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -23,17 +21,21 @@ class ThemeUploadFailureWidget extends StatelessWidget {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Spacer(),
FlowySvg( FlowySvg(
FlowySvgs.close_m, FlowySvgs.close_m,
size: ThemeUploadWidget.iconSize, size: ThemeUploadWidget.iconSize,
color: Theme.of(context).colorScheme.onBackground, color: Theme.of(context).colorScheme.onBackground,
), ),
FlowyText.medium( FlowyText.medium(
LocaleKeys.settings_appearance_themeUpload_failure.tr(), errorMessage,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
ThemeUploadWidget.elementSpacer, ThemeUploadWidget.elementSpacer,
const ThemeUploadLearnMoreButton(),
ThemeUploadWidget.elementSpacer,
ThemeUploadButton(color: Theme.of(context).colorScheme.error), ThemeUploadButton(color: Theme.of(context).colorScheme.error),
ThemeUploadWidget.elementSpacer,
], ],
), ),
); );

View File

@ -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(),
),
),
);
}
}
},
),
),
);
}
}

View File

@ -13,7 +13,7 @@ class ThemeUploadWidget extends StatefulWidget {
static const double borderRadius = 8; static const double borderRadius = 8;
static const double buttonFontSize = 14; 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 EdgeInsets padding = EdgeInsets.all(12.0);
static const Size iconSize = Size.square(48); static const Size iconSize = Size.square(48);
static const Widget elementSpacer = SizedBox(height: 12); static const Widget elementSpacer = SizedBox(height: 12);
@ -41,9 +41,10 @@ class _ThemeUploadWidgetState extends State<ThemeUploadWidget> {
key: Key('upload_theme_loading_widget'), key: Key('upload_theme_loading_widget'),
); );
}, },
compilationFailure: () { compilationFailure: (errorMessage) {
child = const ThemeUploadFailureWidget( child = ThemeUploadFailureWidget(
key: Key('upload_theme_failure_widget'), key: const Key('upload_theme_failure_widget'),
errorMessage: errorMessage,
); );
}, },
compilationSuccess: () { compilationSuccess: () {

View File

@ -1,21 +1,13 @@
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/workspace/presentation/settings/widgets/theme_upload/theme_upload_button.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.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';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'theme_upload_view.dart';
class UploadNewThemeWidget extends StatelessWidget { class UploadNewThemeWidget extends StatelessWidget {
const UploadNewThemeWidget({super.key}); const UploadNewThemeWidget({super.key});
static const learnMoreRedirect =
'https://appflowy.gitbook.io/docs/essential-documentation/themes';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@ -28,6 +20,7 @@ class UploadNewThemeWidget extends StatelessWidget {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Spacer(),
FlowySvg( FlowySvg(
FlowySvgs.folder_m, FlowySvgs.folder_m,
size: ThemeUploadWidget.iconSize, size: ThemeUploadWidget.iconSize,
@ -38,51 +31,12 @@ class UploadNewThemeWidget extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
ThemeUploadWidget.elementSpacer, ThemeUploadWidget.elementSpacer,
SizedBox( const ThemeUploadLearnMoreButton(),
height: ThemeUploadWidget.buttonSize.height, ThemeUploadWidget.elementSpacer,
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 Divider(), const Divider(),
ThemeUploadWidget.elementSpacer, ThemeUploadWidget.elementSpacer,
const ThemeUploadButton(), const ThemeUploadButton(),
ThemeUploadWidget.elementSpacer,
], ],
), ),
); );

View File

@ -64,14 +64,14 @@ class DefaultColorScheme extends FlowyColorScheme {
onPrimary: _white, onPrimary: _white,
hoverBG1: _lightBg2, hoverBG1: _lightBg2,
hoverBG2: _lightHover, hoverBG2: _lightHover,
hoverBG3: _lightShader6,
hoverFG: _lightShader1, hoverFG: _lightShader1,
questionBubbleBG: _lightSelector, questionBubbleBG: _lightSelector,
hoverBG3: _lightShader6,
progressBarBGColor: _lightTint9, progressBarBGColor: _lightTint9,
toolbarColor: _lightShader1, toolbarColor: _lightShader1,
toggleButtonBGColor: _lightShader5, toggleButtonBGColor: _lightShader5,
calendarWeekendBGColor: const Color(0xFFFBFBFC), calendarWeekendBGColor: const Color(0xFFFBFBFC),
gridRowCountColor: const Color(0xff000000), gridRowCountColor: _lightShader1,
); );
const DefaultColorScheme.dark() const DefaultColorScheme.dark()
@ -122,7 +122,7 @@ class DefaultColorScheme extends FlowyColorScheme {
progressBarBGColor: _darkShader3, progressBarBGColor: _darkShader3,
toolbarColor: _darkInput, toolbarColor: _darkInput,
toggleButtonBGColor: _darkShader1, toggleButtonBGColor: _darkShader1,
calendarWeekendBGColor: const Color(0xff121212), calendarWeekendBGColor: _darkShader1,
gridRowCountColor: _darkMain1, gridRowCountColor: _darkShader5,
); );
} }

View File

@ -37,8 +37,9 @@ class DynamicPluginBloc extends Bloc<DynamicPluginEvent, DynamicPluginState> {
return; return;
} }
await FlowyPluginService.instance.addPlugin(plugin); await FlowyPluginService.instance.addPlugin(plugin);
} on PluginCompilationException { } on PluginCompilationException catch (exception) {
return emit(const DynamicPluginState.compilationFailure()); return emit(DynamicPluginState.compilationFailure(
errorMessage: exception.message));
} }
emit(const DynamicPluginState.compilationSuccess()); emit(const DynamicPluginState.compilationSuccess());

View File

@ -11,7 +11,8 @@ class DynamicPluginState with _$DynamicPluginState {
required Iterable<FlowyDynamicPlugin> plugins, required Iterable<FlowyDynamicPlugin> plugins,
}) = Ready; }) = Ready;
const factory DynamicPluginState.processing() = _Processing; const factory DynamicPluginState.processing() = _Processing;
const factory DynamicPluginState.compilationFailure() = _CompilationFailure; const factory DynamicPluginState.compilationFailure(
{required String errorMessage}) = _CompilationFailure;
const factory DynamicPluginState.deletionFailure({ const factory DynamicPluginState.deletionFailure({
required String path, required String path,
}) = _DeletionFailure; }) = _DeletionFailure;

View File

@ -74,21 +74,21 @@ class FlowyDynamicPlugin {
/// compilation error will be thrown during the construction of this object. /// compilation error will be thrown during the construction of this object.
Future<Directory> encode() async { Future<Directory> encode() async {
final fs = MemoryFileSystem(); final fs = MemoryFileSystem();
final result = fs.directory(_fsPluginName)..createSync(); final directory = fs.directory(_fsPluginName)..createSync();
final lightPath = p.join(_fsPluginName, '$name.$lightExtension'); final lightThemeFileName = '$name.$lightExtension';
result.childFile(lightPath).createSync(); directory.childFile(lightThemeFileName).createSync();
result directory
.childFile(lightPath) .childFile(lightThemeFileName)
.writeAsStringSync(jsonEncode(theme!.lightTheme.toJson())); .writeAsStringSync(jsonEncode(theme!.lightTheme.toJson()));
final darkPath = p.join(_fsPluginName, '$name.$darkExtension'); final darkThemeFileName = '$name.$darkExtension';
result.childFile(darkPath).createSync(); directory.childFile(darkThemeFileName).createSync();
result directory
.childFile(p.join(_fsPluginName, '$name.$darkExtension')) .childFile(darkThemeFileName)
.writeAsStringSync(jsonEncode(theme!.darkTheme.toJson())); .writeAsStringSync(jsonEncode(theme!.darkTheme.toJson()));
return result; return directory;
} }
/// Theme plugins should have the following format. /// Theme plugins should have the following format.
@ -119,13 +119,32 @@ class FlowyDynamicPlugin {
event is File && p.basename(event.path).contains(darkExtension)) event is File && p.basename(event.path).contains(darkExtension))
.first as File; .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( final theme = AppTheme(
themeName: name, themeName: name,
builtIn: false, builtIn: false,
lightTheme: lightTheme: lightTheme,
FlowyColorScheme.fromJson(jsonDecode(await light.readAsString())), darkTheme: darkTheme,
darkTheme:
FlowyColorScheme.fromJson(jsonDecode(await dark.readAsString())),
); );
return FlowyDynamicPlugin._( return FlowyDynamicPlugin._(

View File

@ -76,8 +76,8 @@ class SecondaryButton extends StatelessWidget {
minWidth: size.width, minWidth: size.width,
minHeight: size.height, minHeight: size.height,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
bgColor: Theme.of(context).colorScheme.surface, bgColor: Colors.transparent,
outlineColor: Theme.of(context).colorScheme.primary, outlineColor: Theme.of(context).colorScheme.onBackground,
borderRadius: mode.borderRadius, borderRadius: mode.borderRadius,
onPressed: onPressed, onPressed: onPressed,
child: child, child: child,

View File

@ -356,7 +356,6 @@
"button": "Upload", "button": "Upload",
"uploadTheme": "Upload theme", "uploadTheme": "Upload theme",
"description": "Upload your own AppFlowy theme using the button below.", "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...", "loading": "Please wait while we validate and upload your theme...",
"uploadSuccess": "Your theme was uploaded successfully", "uploadSuccess": "Your theme was uploaded successfully",
"deletionFailure": "Failed to delete the theme. Try to delete it manually.", "deletionFailure": "Failed to delete the theme. Try to delete it manually.",