Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Dynamically Load Themes in AppFlowy #2670

Merged
merged 92 commits into from
Jul 3, 2023

Conversation

a-wallen
Copy link
Contributor

@a-wallen a-wallen commented May 31, 2023

Feature Preview

dynamic_theme_demo.mp4

Loads themes from a JSON file at runtime.

TODO

  • Run build_runner in flowy_infra to pass CI
  • Fix InvalidType build_runner errors.
  • Provide default values or throw an error if FlowyColorScheme API changes and the old theme is incompatible with the new API.
  • Load the themes from a different directory (for example, path_provider getApplicationDocumentsDirectory()).
  • Allow users to drag and drop themes via the GUI (in the appearance settings menu).
AppFlowy.2023-06-06.20-56-36.mp4
  • Delete uploaded themes.
AppFlowy.2023-06-09.12-05-53.mp4
  • Better prompt messages
  • Better error messages
  • Fix all merge conflicts from the recent merge of develop into main
  • Refactor (code style)
  • Widget/integration test for previous use cases.
  • Users should be able to browse available JSON themes on a website.

Issues #1070. It does not fix but makes it easier to experiment with new themes.


PR Checklist

  • My code adheres to the AppFlowy Style Guide
  • I've listed at least one issue that this PR fixes in the description above.
  • I've added a test(s) to validate changes in this PR, or this PR only contains semantic changes.
  • All existing tests are passing.

@a-wallen a-wallen requested a review from hyj1204 May 31, 2023 04:57
@annieappflowy annieappflowy added the new feature New feature or request label Jun 1, 2023
class ColorConverter implements JsonConverter<Color, String> {
const ColorConverter();

static const Color fallback = Colors.transparent;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the FlowyColorScheme API changes. The parser will provide transparent as the default value.

Another way to fail silently is to pass over dynamic themes that can't be parsed. I.e., they won't be included if the app is updated since the theme is invalid. I thought this would be the best way to fail silently.

/// This class can be modified to support loading node widget builders and other
/// plugins that are dynamically loaded at runtime for the editor. For now,
/// it only supports loading app themes.
class FlowyPlugin {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about FlowyDynamicPlugin?

@a-wallen
Copy link
Contributor Author

a-wallen commented Jun 4, 2023

Note: after adding json_annotation, json_serializable, and build_runner from appflowy's pubspec to flowy_infra's pubspec causes 3 different versions of the dart analyzer to download to the pub cache. Since the InvalidType error suggests that the Locale type is unresolved, the issue is probably coming from a broken analyzer. The current main @71d118904871d5526c13479ca2e672d4dd81b6ea downloads analyzer version 5.2.0 and _fe_analyzer_shared-50.0. Adding the same packages to flowy_infra downloads analyzer 5.13 and _fe_analyzer_shared-61.0.0.

@a-wallen a-wallen force-pushed the dynamic_theme_plugin branch 2 times, most recently from 55ffcf1 to 5e379fd Compare June 5, 2023 23:38
@a-wallen a-wallen force-pushed the dynamic_theme_plugin branch 2 times, most recently from ede733f to 7e88ee7 Compare June 12, 2023 21:56
@a-wallen a-wallen marked this pull request as ready for review June 15, 2023 06:16
@AppFlowy-IO AppFlowy-IO deleted a comment from codecov bot Jun 15, 2023
@codecov
Copy link

codecov bot commented Jun 15, 2023

Codecov Report

Merging #2670 (b3b99a5) into main (2c513be) will decrease coverage by 0.52%.
The diff coverage is 31.53%.

@@            Coverage Diff             @@
##             main    #2670      +/-   ##
==========================================
- Coverage   67.28%   66.77%   -0.52%     
==========================================
  Files         409      414       +5     
  Lines       19502    19664     +162     
==========================================
+ Hits        13122    13130       +8     
- Misses       6380     6534     +154     
Flag Coverage Δ
appflowy_flutter_integrateion_test 64.67% <31.15%> (-0.56%) ⬇️
appflowy_flutter_unit_test 12.06% <14.23%> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...wy_flutter/lib/plugins/document/document_page.dart 70.49% <ø> (ø)
...ation/editor_plugins/base/link_to_page_widget.dart 73.91% <0.00%> (ø)
...ditor_plugins/header/custom_cover_picker_bloc.dart 61.53% <ø> (ø)
...ion/editor_plugins/image/image_selection_menu.dart 19.04% <ø> (ø)
...gins/document/presentation/share/share_button.dart 91.66% <ø> (ø)
...er/lib/user/presentation/folder/folder_widget.dart 42.34% <ø> (ø)
..._flutter/lib/workspace/application/appearance.dart 84.74% <0.00%> (-0.18%) ⬇️
...tion/home/menu/app/header/import/import_panel.dart 83.33% <ø> (ø)
...widgets/settings_file_customize_location_view.dart 84.53% <ø> (ø)
...ettings/widgets/settings_file_exporter_widget.dart 1.60% <ø> (ø)
... and 14 more

... and 4 files with indirect coverage changes

@a-wallen
Copy link
Contributor Author

Working on testing some of the plugin infrastructure but FlowyDynamicPlugin.decode is making it a bit hard for me. I will resume tomorrow.


import 'theme_upload_view.dart';

class ThemeUploadDecoration extends StatelessWidget {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better if we moved this widget to flowy_infra_ui and made it a configurable widget? Then it could be used for theme upload and file upload in the database.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will hold off on this because I'd like to keep all of the theme upload widgets together. Some of the theme upload widgets use LocaleKeys which requires AppFlowy.

@annieappflowy
Copy link
Collaborator

Can we aim to release the MVP by the end of next week?

@a-wallen
Copy link
Contributor Author

Sure 👍

@a-wallen
Copy link
Contributor Author

AppFlowy.2023-06-29.12-18-23.mp4

Comment on lines +110 to +120
final light = src
.listSync()
.where((event) =>
event is File && p.basename(event.path).contains(lightExtension))
.first as File;

final dark = src
.listSync()
.where((event) =>
event is File && p.basename(event.path).contains(darkExtension))
.first as File;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking at this and I can't help but think if there is a better way to do it.

The only way I could think of introduced ~15 more lines of code though, not sure it's worth it.

~25 lines of code version
    final srcAsList = src.listSync();

    File? light;
    File? dark;

    for (int i = 0; i < srcAsList.length; i++) {
      if (light != null && dark != null) {
        break;
      }

      final event = srcAsList[i];
      if (event is File) {
        if (p.basename(event.path).contains(lightExtension)) {
          light = event;
        } else if (p.basename(event.path).contains(darkExtension)) {
          dark = event;
        }
      }
    }

    if (light == null || dark == null) {
      throw PluginCompilationException(
        'The theme plugin does not contain both a light and dark version: `theme.light.json` and `theme.dark.json`.',
      );
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least this way, we're only listing the directory once. This is a good review comment. Let me try to expand on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha @Xazin, if we're code golfing it then you can try to beat this :)

    final files = src.listSync().whereType<File>().toList();

    File? light, dark;

    for (int i = 0; i < files.length || (light == null && dark == null); ++i) {
      final file = files[i];
      if (p.basename(file.path).contains(lightExtension)) {
        light = file;
      } else if (p.basename(file.path).contains(darkExtension)) {
        dark = file;
      }
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way we still get the benefit of only listing the directory once.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that is indeed better. Semantically it's the same, but more readable.

@appflowy appflowy merged commit 8dfbfe3 into AppFlowy-IO:main Jul 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new feature New feature or request
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

5 participants