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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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(
icon: const FlowySvg(
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/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,
],
),
);

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 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<ThemeUploadWidget> {
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: () {

View File

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

View File

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

View File

@ -37,8 +37,9 @@ class DynamicPluginBloc extends Bloc<DynamicPluginEvent, DynamicPluginState> {
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());

View File

@ -11,7 +11,8 @@ class DynamicPluginState with _$DynamicPluginState {
required Iterable<FlowyDynamicPlugin> 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;

View File

@ -74,21 +74,21 @@ class FlowyDynamicPlugin {
/// compilation error will be thrown during the construction of this object.
Future<Directory> 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._(

View File

@ -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,

View File

@ -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.",