From ff9b3c56c5a36b9b06229cd98fa3a5b0e4edba70 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Sun, 9 Jul 2023 10:03:22 +0700 Subject: [PATCH] feat:IInline math equation (#2949) --- .github/workflows/flutter_ci.yaml | 2 +- .github/workflows/integration_test.yml | 37 ++-- .../assets/images/editor/math.svg | 1 + .../assets/translations/en.json | 1 + .../database_calendar_test.dart | 12 +- .../database_row_page_test.dart | 3 +- .../integration_test/database_share_test.dart | 2 + ...t => document_create_and_delete_test.dart} | 2 +- .../document/document_test_runner.dart | 22 +++ ...rt => document_with_cover_image_test.dart} | 0 ...cument_with_inline_math_equation_test.dart | 65 +++++++ ...ument_with_inline_math_equation_test_1.png | Bin 0 -> 7222 bytes ...ument_with_inline_math_equation_test_2.png | Bin 0 -> 7204 bytes .../integration_test/runner.dart | 25 +-- .../integration_test/util/base.dart | 32 +++- .../util/database_test_op.dart | 15 +- .../util/editor_test_operations.dart | 10 + .../document/presentation/editor_page.dart | 5 +- .../editor_plugins/actions/option_action.dart | 2 +- .../header/document_header_node_widget.dart | 16 +- .../editor_plugins/header/emoji_popover.dart | 2 +- .../editor_plugins/image/image_menu.dart | 2 +- .../inline_math_equation.dart | 173 ++++++++++++++++++ .../inline_math_equation_toolbar_item.dart | 52 ++++++ .../inline_page/inline_page_reference.dart | 2 - .../presentation/editor_plugins/plugins.dart | 24 +-- .../document/presentation/editor_style.dart | 26 ++- .../appflowy_flutter/lib/startup/startup.dart | 5 +- .../lib/startup/tasks/windows.dart | 7 +- .../lib/user/presentation/splash_screen.dart | 2 +- .../settings/application_data_storage.dart | 1 + .../packages/flowy_infra/lib/image.dart | 11 +- frontend/appflowy_flutter/pubspec.lock | 6 +- frontend/appflowy_flutter/pubspec.yaml | 2 +- frontend/appflowy_tauri/src-tauri/src/main.rs | 4 +- frontend/rust-lib/Cargo.lock | 10 + frontend/rust-lib/dart-ffi/src/lib.rs | 4 +- .../rust-lib/flowy-notification/src/lib.rs | 11 ++ .../freezed/generate_freezed.sh | 11 +- frontend/scripts/code_generation/generate.sh | 0 .../generate_language_files.cmd | 2 - .../language_files/generate_language_files.sh | 2 - frontend/scripts/makefile/flutter.toml | 9 +- 43 files changed, 500 insertions(+), 120 deletions(-) create mode 100644 frontend/appflowy_flutter/assets/images/editor/math.svg rename frontend/appflowy_flutter/integration_test/document/{document_test.dart => document_create_and_delete_test.dart} (98%) create mode 100644 frontend/appflowy_flutter/integration_test/document/document_test_runner.dart rename frontend/appflowy_flutter/integration_test/document/{cover_image_test.dart => document_with_cover_image_test.dart} (100%) create mode 100644 frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test.dart create mode 100644 frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test_1.png create mode 100644 frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test_2.png create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart mode change 100644 => 100755 frontend/scripts/code_generation/freezed/generate_freezed.sh mode change 100644 => 100755 frontend/scripts/code_generation/generate.sh mode change 100644 => 100755 frontend/scripts/code_generation/language_files/generate_language_files.sh diff --git a/.github/workflows/flutter_ci.yaml b/.github/workflows/flutter_ci.yaml index 6cc1967ade..ebb5f2c5a2 100644 --- a/.github/workflows/flutter_ci.yaml +++ b/.github/workflows/flutter_ci.yaml @@ -134,6 +134,6 @@ jobs: fail_ci_if_error: true verbose: true os: ${{ matrix.os }} - attempt_limit: 5 + attempt_limit: 20 attempt_delay: 10000 diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 4ccca8f067..3ca838631e 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -29,25 +29,31 @@ concurrency: cancel-in-progress: true jobs: - tests: + build: if: github.event.pull_request.draft != true strategy: + fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - + include: + - os: ubuntu-latest + flutter_profile: development-linux-x86_64 + target: x86_64-unknown-linux-gnu + - os: windows-latest + flutter_profile: development-windows-x86 + target: x86_64-pc-windows-msvc runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: "stable-2022-04-07" + - name: Checkout source code + uses: actions/checkout@v2 - name: Install Rust toolchain id: rust_toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ env.RUST_TOOLCHAIN }} + target: ${{ matrix.target }} override: true profile: minimal @@ -64,6 +70,7 @@ jobs: prefix-key: ${{ matrix.os }} workspaces: | frontend/rust-lib + cache-all-crates: true - uses: davidB/rust-cargo-make@v1 with: @@ -87,30 +94,24 @@ jobs: cargo make appflowy-flutter-deps-tools shell: bash - - name: Config Flutter + - name: Enable Flutter Desktop run: | if [ "$RUNNER_OS" == "Linux" ]; then flutter config --enable-linux-desktop elif [ "$RUNNER_OS" == "macOS" ]; then flutter config --enable-macos-desktop elif [ "$RUNNER_OS" == "Windows" ]; then + git config --system core.longpaths true flutter config --enable-windows-desktop fi shell: bash - - name: Build Test lib + - name: Build AppFlowy working-directory: frontend run: | - if [ "$RUNNER_OS" == "Linux" ]; then - cargo make --profile development-linux-x86_64 appflowy-dev - elif [ "$RUNNER_OS" == "macOS" ]; then - cargo make --profile development-mac-x86_64 appflowy-dev - elif [ "$RUNNER_OS" == "Windows" ]; then - cargo make --profile development-windows-x86 appflowy-dev - fi - shell: bash + cargo make --profile ${{ matrix.flutter_profile }} appflowy-dev - - name: Run AppFlowy tests + - name: Run Flutter integration tests working-directory: frontend/appflowy_flutter run: | if [ "$RUNNER_OS" == "Linux" ]; then @@ -135,5 +136,5 @@ jobs: fail_ci_if_error: true verbose: true os: ${{ matrix.os }} - attempt_limit: 5 + attempt_limit: 20 attempt_delay: 10000 \ No newline at end of file diff --git a/frontend/appflowy_flutter/assets/images/editor/math.svg b/frontend/appflowy_flutter/assets/images/editor/math.svg new file mode 100644 index 0000000000..32e79a21ed --- /dev/null +++ b/frontend/appflowy_flutter/assets/images/editor/math.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/appflowy_flutter/assets/translations/en.json b/frontend/appflowy_flutter/assets/translations/en.json index fb885a1f6c..fa5bdca791 100644 --- a/frontend/appflowy_flutter/assets/translations/en.json +++ b/frontend/appflowy_flutter/assets/translations/en.json @@ -426,6 +426,7 @@ "smartEditCouldNotFetchKey": "Could not fetch OpenAI key", "smartEditDisabled": "Connect OpenAI in Settings", "discardResponse": "Do you want to discard the AI responses?", + "createInlineMathEquation": "Create equation", "cover": { "changeCover": "Change Cover", "colors": "Colors", diff --git a/frontend/appflowy_flutter/integration_test/database_calendar_test.dart b/frontend/appflowy_flutter/integration_test/database_calendar_test.dart index cd096db6e8..5d4561925c 100644 --- a/frontend/appflowy_flutter/integration_test/database_calendar_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_calendar_test.dart @@ -107,7 +107,7 @@ void main() { // Make sure that the event is edited tester.assertNumberOfEventsInCalendar(1, title: 'hello world'); - tester.assertNumberofEventsOnSpecificDay(2, DateTime.now()); + tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now()); // Click on the event await tester.openCalendarEvent(index: 1); @@ -119,7 +119,7 @@ void main() { // Check that there are 2 events tester.assertNumberOfEventsInCalendar(2, title: 'hello world'); - tester.assertNumberofEventsOnSpecificDay(3, DateTime.now()); + tester.assertNumberOfEventsOnSpecificDay(3, DateTime.now()); // Delete an event await tester.openCalendarEvent(index: 1); @@ -127,7 +127,7 @@ void main() { // Check that there is 1 event tester.assertNumberOfEventsInCalendar(1, title: 'hello world'); - tester.assertNumberofEventsOnSpecificDay(2, DateTime.now()); + tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now()); }); testWidgets('rescheduling events', (tester) async { @@ -150,7 +150,7 @@ void main() { // Make sure that the event has been rescheduled to the new date final sameDayNextWeek = firstOfThisMonth.add(const Duration(days: 7)); tester.assertNumberOfEventsInCalendar(1); - tester.assertNumberofEventsOnSpecificDay(1, sameDayNextWeek); + tester.assertNumberOfEventsOnSpecificDay(1, sameDayNextWeek); // Delete the event await tester.openCalendarEvent(index: 0, date: sameDayNextWeek); @@ -162,7 +162,7 @@ void main() { await tester.dismissRowDetailPage(); // Make sure that the event is today - tester.assertNumberofEventsOnSpecificDay(1, today); + tester.assertNumberOfEventsOnSpecificDay(1, today); // Click on the event await tester.openCalendarEvent(index: 0); @@ -183,7 +183,7 @@ void main() { // Make sure that the event is edited tester.assertNumberOfEventsInCalendar(1); - tester.assertNumberofEventsOnSpecificDay(1, newDate); + tester.assertNumberOfEventsOnSpecificDay(1, newDate); }); }); } diff --git a/frontend/appflowy_flutter/integration_test/database_row_page_test.dart b/frontend/appflowy_flutter/integration_test/database_row_page_test.dart index bfd5a5416b..42f291586e 100644 --- a/frontend/appflowy_flutter/integration_test/database_row_page_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_row_page_test.dart @@ -172,9 +172,10 @@ void main() { // Focus on the editor final textBlock = find.byType(TextBlockComponentWidget); await tester.tapAt(tester.getCenter(textBlock)); + await tester.pumpAndSettle(); // Input some text - const inputText = 'Hello world'; + const inputText = 'Hello World'; await tester.ime.insertText(inputText); expect( find.textContaining(inputText, findRichText: true), diff --git a/frontend/appflowy_flutter/integration_test/database_share_test.dart b/frontend/appflowy_flutter/integration_test/database_share_test.dart index edb6baceaa..d6571875b9 100644 --- a/frontend/appflowy_flutter/integration_test/database_share_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_share_test.dart @@ -10,6 +10,8 @@ void main() { group('database', () { testWidgets('import v0.2.0 database data', (tester) async { await tester.openV020database(); + // wait the database data is loaded + await tester.pumpAndSettle(const Duration(microseconds: 500)); // check the text cell final textCells = ['A', 'B', 'C', 'D', 'E', '', '', '', '', '']; diff --git a/frontend/appflowy_flutter/integration_test/document/document_test.dart b/frontend/appflowy_flutter/integration_test/document/document_create_and_delete_test.dart similarity index 98% rename from frontend/appflowy_flutter/integration_test/document/document_test.dart rename to frontend/appflowy_flutter/integration_test/document/document_create_and_delete_test.dart index cd366e75a3..c447494904 100644 --- a/frontend/appflowy_flutter/integration_test/document/document_test.dart +++ b/frontend/appflowy_flutter/integration_test/document/document_create_and_delete_test.dart @@ -9,7 +9,7 @@ import '../util/util.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - group('document', () { + group('create and delete the document', () { testWidgets('create a new document when launching app in first time', (tester) async { await tester.initializeAppFlowy(); diff --git a/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart b/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart new file mode 100644 index 0000000000..8ab64d88b8 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart @@ -0,0 +1,22 @@ +import 'package:integration_test/integration_test.dart'; + +import 'document_create_and_delete_test.dart' + as document_create_and_delete_test; +import 'document_with_cover_image_test.dart' as document_with_cover_image_test; +import 'document_with_database_test.dart' as document_with_database_test; +import 'document_with_inline_math_equation_test.dart' + as document_with_inline_math_equation_test; +import 'document_with_inline_page_test.dart' as document_with_inline_page_test; +import 'edit_document_test.dart' as document_edit_test; + +void startTesting() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + // Document integration tests + document_create_and_delete_test.main(); + document_edit_test.main(); + document_with_database_test.main(); + document_with_inline_page_test.main(); + document_with_inline_math_equation_test.main(); + document_with_cover_image_test.main(); +} diff --git a/frontend/appflowy_flutter/integration_test/document/cover_image_test.dart b/frontend/appflowy_flutter/integration_test/document/document_with_cover_image_test.dart similarity index 100% rename from frontend/appflowy_flutter/integration_test/document/cover_image_test.dart rename to frontend/appflowy_flutter/integration_test/document/document_with_cover_image_test.dart diff --git a/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test.dart b/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test.dart new file mode 100644 index 0000000000..00f791cfbb --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test.dart @@ -0,0 +1,65 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../util/ime.dart'; +import '../util/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + TestWidgetsFlutterBinding.ensureInitialized(); + + group('inline math equation in document', () { + testWidgets('insert an inline math equation', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + // create a new document + await tester.createNewPageWithName( + ViewLayoutPB.Document, + LocaleKeys.document_plugins_createInlineMathEquation.tr(), + ); + + // tap the first line of the document + await tester.editor.tapLineOfEditorAt(0); + // insert a inline page + const formula = 'E = MC ^ 2'; + await tester.ime.insertText(formula); + await tester.editor.updateSelection( + Selection.single(path: [0], startOffset: 0, endOffset: formula.length), + ); + + // tap the inline math equation button + final inlineMathEquationButton = find.byTooltip( + LocaleKeys.document_plugins_createInlineMathEquation.tr(), + ); + await tester.tapButton(inlineMathEquationButton); + + // expect to see the math equation block + final inlineMathEquation = find.byType(InlineMathEquation); + expect(inlineMathEquation, findsOneWidget); + + // tap it and update the content + await tester.tapButton(inlineMathEquation); + final textFormField = find.descendant( + of: find.byType(MathInputTextField), + matching: find.byType(TextFormField), + ); + const newFormula = 'E = MC ^ 3'; + await tester.enterText(textFormField, newFormula); + await tester.tapButton( + find.descendant( + of: find.byType(MathInputTextField), + matching: find.byType(FlowyButton), + ), + ); + await tester.pumpAndSettle(); + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test_1.png b/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8795110cf95d90646b0a443d91d05cdd1d7980b3 GIT binary patch literal 7222 zcmeI$dr(qY90zbJ)26c8oF1DGYQ062S&K2I_}EO-tA#t*(??io`3N+@lE7@&>7A7q z@{wV=ns_s@bd?NH+*%V$sW41UZAY_E34=fd(S@D;cmHc<|GB?^?wmVw&i8jdXXgBI zF8)jmw4J+RE((RR1%v!aDAa5x6v~P|$J+d7S>Rlm`7?t?3iLzq+Z>eUffdaU+%?Bs ztT{0kP$>KNp#RogX}3nYqpn2lUScykU9a8RXtnn2Xwg!RCOY$aGB4hi8!?ZDg##7? z7cMSBvsOM`7BtY9hx)N8Zsv@`o3}gd+I;=e;vM60YCsaX?(Pdp=J?i|i>~CMzPsQ4 zQ^?UlDmt!p3k3ss(qHzeKfJ3}>}OVTjkPJnndWwo`VWCga>1=z4xV3)wc++OZG@0e zIZS8vb9tx8j10mo^Q89;6%NDJd1Rg&gOn4qm$bmX-SKO&K2BROuWHO+O~C35CG8}f zPi-xur{RN8+zp-D29BF1}8b(@%{h~y4D0&GaMuwc{nVukELS9pyx zSWEO`9>-?cK#x7K4I8(Gt#4A?XHZtP%{C`X{vdR1x*Tw*d!H5!@3C{7k5I=_=6|d| zkjMusI%)$8o|s~yg6nT{*n_^VN{rc->7K9GObV~OQ})aM%y z_n_2UmE?TDO}Y^(k*iD}xbxF-`6~)xQ{RTs&qpzxIWejt$@tT@8+ctmxB%!R;BYv+ zpiJH~p(}9>X_akE$`au)CSJH^a6}>vF}P1r$c*3GMO7c)ER&JX&@4ftKDe~W=tlrZ zX;0B8KDPNrVojqWu-4G5@4i~qUU(M;OQgv$OvMFeU zFEwoY56c4r%$;X6&x;J_W@uWJEVEBJ)Y$$?Ijt}Fcy|((uSSVSsfnW^i!ac8d-fr z{cbY0*!iTaUQK5T)}(^ix=Ojq7SOd#7%Ue;^cTws=M<)Xz5!#g69<_T?I2MwMh{Mp z3ptsR3f40_KQ#|IzsagNVA>A3PqD>`ElaREo0=T=-~?-uYtWEmSWV=XcWcLZr3+d| z#2M5cNwle5sAC+WQYYcwIMv5XC(0Smi}t~c-4Je?r+_DR3g3uM6k_(okj3uL za@5uA)gzn)zsv$b&Bqp|X+)d^} z7}v*o+KG*9QQH3UiR!Urx@E&Yl9q?mGtTxIRbvxf&DYp29?MUyS5J|jp+#2(fd#6w zZ-wS9XG6PflBVA}QTQWtW*FSbA@M_p-hk)WJMIqhJ8$otE}y>0U7rZ|gKa??Kgcc^ z9Fz3gHHeP)Gt@5reU=~>C+ll2Wg9~EuJQ9@Vl$46{$)Gt%2Mtdvs`INAp(d1B7g`W z0*C-2fCwN0hyWsh2z+w^iEnr2tbZQhR!x)Lfp5M(vLGUW2p|H803v`0{C@&ovk!jX UaC)V0bD^aNFo5XK_ly4ZZzNsy1^@s6 literal 0 HcmV?d00001 diff --git a/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test_2.png b/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test_2.png new file mode 100644 index 0000000000000000000000000000000000000000..de3d4f6113f2ad92aeb3654dd620d98160aeaa39 GIT binary patch literal 7204 zcmeIyZB)`%90zdRnl_!;wn`T!EfHr~p0rX^VyRRAD3O2LEDc?0Sdu+xiui!1Jk8wE zNI*$ULa`qz6|zTHKCAwWVO|l(QDD7ec2D^r>>l{zWmG~bf?7avYLOsG9amV*_Vem zJhZ_*@e9>Fet3Or@VBcAr=3?`3t%enh0BlZut>LNHps21hn)K_L|j;T=QprfqRZGF z_P9}f=@Bd0uylg>$~Tph%IQ|iTC^3Ww?Oq@0!9_DrY2wCHnN`-Pt8v0dOz698m}Vr zGR27Jz2-w1ttH;g7HgE_;Z>L7Z#%nL*-q%2xaeVn;i`f&k>kdVtzbrDQLY{3s+Ntz zlgZ%|X#gGMy@>Cz)y+M-b4-2@NMz@b1Wlf@t$?2QYJs`X6fDrT-QC{289+(esEIv8 z*SIVj62DS${J6MW?B3C2&fTA#4=3IV^7rXC;OQg=yQI75z$RuCobtaz}L zfWu0QnUQ+Cb&P5rhNU@Q*3{r9D-cDbyDR@qwxop(b$|=}&o|MlnVDnlg8pQCaj;m_ zS?=vZ*~m2zZzY{{z-Z^0Eod1B>FVfE=M{(vZ}0;M(i_Hh5h;X&KAIQZ=<0i@UYR;5 zD3;JQ^l74pjKO^+w4#d8(g{D2@}#Y8FPfvNd(-g?&ZC!a?%C&?ubgO>+*}I1+XaKA z?-9?5;eeXiuuKp%GI6#dAeJ*uWr7(R0mnZ@w;^7Qod!tmwlk9_8h&>p)j z$wh|v*bg3}gck;OnxME($s8FdR|~aY{}b@ka^$sT25t)$gp1f2C&`OF0O8glL(7<- zo&SO~KgOB1ZsQgCq`FE&ut|=lTe)^rL^*De&fgCisif+e35hu6yEq|tm_>(!I)=jV z=ac8N48YUNUssT~fln^2p2G?wTWQ<~%&-&uK#8Uh9Lic{N`84Xaq8~FXv`V%+{({f}+gcX`K! ze7uVc9VxHz#{Sg`q?ZilhT#Albq7ZKZZW-O^EjePGd^p7@Ft0xt^V++M;Ob`#pB9` zo=ODDohm290cQTFS>Tk$!GFu?u0Ef|83mJ>EkP?=O#PzSpmu1eGL}FW3gCzXIkhbX z19eqlGebsD!aK+U+YXQ@_0`1nqb=1`3Xi4BhmT%VD6TXS$lZN#ha2`%cGcooCrbRj zgM=v+#q_}EgrXQ{vRb!SOAEZc#6U$~Q+4-Qm?2)rn;-_FG5bJpVN{7MtDF6uLF>A0 z93-SGHJ$lgZ^CDH)VOE!(~_K$#N|;QQ$?vWhg&yyJ#OLy*rnK{`Hu_A>$&O8tv>^t zSV{UMXCB~U=7QE1F*Q9_RjwF;Y*wf8$ieIIHhS}CeJ`T@<>`V=ZUST$D!PtZg$&Hg zD;kS?J5mAmIkM4uFH@xI{u_{pk3OpXqv?L)3VYKN>hTRVYNz=*VdDw4?=~+c-P6B6 zX*gpfNAB8(a^(?qS5jV(g2`u^<$jEm`x51Xy@xTjOW(Suv9n^by;fZ|ksKy_Ev4lirFlo0+A32V4|P+a zW*;LT07NiVezp|X_SC4*mxvTHWQ_}DgX}c7T0&M$BM5CDhf3F6J88~gt;h>mGw{kP z(|)^ULj%S;L*+v1HR|AMJM%77kN_kA2|xmn03-kjKmw2eBmfCO0-r{}AV&MFH($MS w#+VlP*{2DCZiNIO0Z0H6fCM0c|5;#a)#i&*(lo***&Gb~v?r)O;Ma411AHv*^#A|> literal 0 HcmV?d00001 diff --git a/frontend/appflowy_flutter/integration_test/runner.dart b/frontend/appflowy_flutter/integration_test/runner.dart index 8dc301ddbd..934e213842 100644 --- a/frontend/appflowy_flutter/integration_test/runner.dart +++ b/frontend/appflowy_flutter/integration_test/runner.dart @@ -1,23 +1,19 @@ import 'package:integration_test/integration_test.dart'; -import 'switch_folder_test.dart' as switch_folder_test; -import 'document/document_test.dart' as document_test; -import 'document/cover_image_test.dart' as cover_image_test; -import 'share_markdown_test.dart' as share_markdown_test; -import 'import_files_test.dart' as import_files_test; -import 'document/document_with_database_test.dart' - as document_with_database_test; -import 'document/edit_document_test.dart' as edit_document_test; +import 'database_calendar_test.dart' as database_calendar_test; import 'database_cell_test.dart' as database_cell_test; import 'database_field_test.dart' as database_field_test; -import 'database_share_test.dart' as database_share_test; +import 'database_filter_test.dart' as database_filter_test; import 'database_row_page_test.dart' as database_row_page_test; import 'database_row_test.dart' as database_row_test; import 'database_setting_test.dart' as database_setting_test; -import 'database_filter_test.dart' as database_filter_test; -import 'database_view_test.dart' as database_view_test; -import 'database_calendar_test.dart' as database_calendar_test; +import 'database_share_test.dart' as database_share_test; import 'database_sort_test.dart' as database_sort_test; +import 'database_view_test.dart' as database_view_test; +import 'document/document_test_runner.dart' as document_test_runner; +import 'import_files_test.dart' as import_files_test; +import 'share_markdown_test.dart' as share_markdown_test; +import 'switch_folder_test.dart' as switch_folder_test; /// The main task runner for all integration tests in AppFlowy. /// @@ -33,10 +29,7 @@ void main() { import_files_test.main(); // Document integration tests - cover_image_test.main(); - document_test.main(); - document_with_database_test.main(); - edit_document_test.main(); + document_test_runner.startTesting(); // Database integration tests database_cell_test.main(); diff --git a/frontend/appflowy_flutter/integration_test/util/base.dart b/frontend/appflowy_flutter/integration_test/util/base.dart index 0c4306cc5f..9a6a199b21 100644 --- a/frontend/appflowy_flutter/integration_test/util/base.dart +++ b/frontend/appflowy_flutter/integration_test/util/base.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; class FlowyTestContext { FlowyTestContext({ @@ -23,7 +24,10 @@ extension AppFlowyTestBase on WidgetTester { Future initializeAppFlowy({ // use to append after the application data directory String? pathExtension, + Size windowsSize = const Size(1600, 1200), }) async { + binding.setSurfaceSize(windowsSize); + mockHotKeyManagerHandlers(); final directory = await mockApplicationDataStorage( pathExtension: pathExtension, @@ -60,7 +64,7 @@ extension AppFlowyTestBase on WidgetTester { final dir = await getTemporaryDirectory(); // Use a random uuid to avoid conflict. - String path = '${dir.path}/appflowy_integration_test/${uuid()}'; + String path = p.join(dir.path, 'appflowy_integration_test', uuid()); if (pathExtension != null && pathExtension.isNotEmpty) { path = '$path/$pathExtension'; } @@ -78,7 +82,7 @@ extension AppFlowyTestBase on WidgetTester { Finder finder, { int? pointer, int buttons = kPrimaryButton, - bool warnIfMissed = true, + bool warnIfMissed = false, int milliseconds = 500, }) async { await tap( @@ -123,6 +127,18 @@ extension AppFlowyTestBase on WidgetTester { return; } + Future doubleTapAt( + Offset location, { + int? pointer, + int buttons = kPrimaryButton, + int milliseconds = 500, + }) async { + await tapAt(location, pointer: pointer, buttons: buttons); + await pump(kDoubleTapMinTime); + await tapAt(location, pointer: pointer, buttons: buttons); + await pumpAndSettle(Duration(milliseconds: milliseconds)); + } + Future doubleTapButton( Finder finder, { int? pointer, @@ -130,20 +146,22 @@ extension AppFlowyTestBase on WidgetTester { bool warnIfMissed = true, int milliseconds = 500, }) async { - await tapButton( + await tap( finder, pointer: pointer, buttons: buttons, warnIfMissed: warnIfMissed, - milliseconds: kDoubleTapMinTime.inMilliseconds, ); - await tapButton( + + await pump(kDoubleTapMinTime); + + await tap( finder, - pointer: pointer, buttons: buttons, + pointer: pointer, warnIfMissed: warnIfMissed, - milliseconds: milliseconds, ); + await pumpAndSettle(Duration(milliseconds: milliseconds)); } Future wait(int milliseconds) async { diff --git a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart index f8fbedc3c7..b7c73afe92 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart @@ -453,8 +453,15 @@ extension AppFlowyDatabaseTest on WidgetTester { } Future dismissRowDetailPage() async { - await sendKeyEvent(LogicalKeyboardKey.escape); + // use tap empty area instead of clicking ESC to dismiss the row detail page + // sometimes, the ESC key is not working. + await simulateKeyEvent(LogicalKeyboardKey.escape); await pumpAndSettle(); + final findRowDetailPage = find.byType(RowDetailPage); + if (findRowDetailPage.evaluate().isNotEmpty) { + await tapAt(const Offset(0, 0)); + await pumpAndSettle(); + } } Future editTitleInRowDetailPage(String title) async { @@ -1031,7 +1038,7 @@ extension AppFlowyDatabaseTest on WidgetTester { expect(findEvents, findsNWidgets(number)); } - void assertNumberofEventsOnSpecificDay( + void assertNumberOfEventsOnSpecificDay( int number, DateTime date, { String? title, @@ -1058,8 +1065,8 @@ extension AppFlowyDatabaseTest on WidgetTester { final todayCell = find.byWidgetPredicate( (widget) => widget is CalendarDayCard && isSameDay(date, widget.date), ); - - await doubleTapButton(todayCell); + final location = getTopLeft(todayCell).translate(10, 10); + await doubleTapAt(location); } Future openCalendarEvent({required index, DateTime? date}) async { diff --git a/frontend/appflowy_flutter/integration_test/util/editor_test_operations.dart b/frontend/appflowy_flutter/integration_test/util/editor_test_operations.dart index 190b68246b..cb015709cc 100644 --- a/frontend/appflowy_flutter/integration_test/util/editor_test_operations.dart +++ b/frontend/appflowy_flutter/integration_test/util/editor_test_operations.dart @@ -165,4 +165,14 @@ class EditorOperations { ); await tester.tapButton(atMenuItem); } + + /// Update the editor's selection + Future updateSelection(Selection selection) async { + final editorState = getCurrentEditorState(); + editorState.updateSelectionWithReason( + selection, + reason: SelectionUpdateReason.uiEvent, + ); + await tester.pumpAndSettle(const Duration(milliseconds: 200)); + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index 1372de49de..f30063be9b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -49,9 +49,10 @@ class _AppFlowyEditorPageState extends State { quoteItem, bulletedListItem, numberedListItem, + inlineMathEquationItem, linkItem, - textColorItem, - highlightColorItem, + buildTextColorItem(), + buildHighlightColorItem(), ]; late final List slashMenuItems; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart index 1c23c0b2ba..1f80b82034 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart @@ -1,7 +1,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart index 3bd66ddc81..7386aa42e9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; @@ -43,20 +43,6 @@ enum CoverType { } } -class DocumentHeaderNodeWidgetBuilder implements NodeWidgetBuilder { - @override - Widget build(NodeWidgetContext context) { - return DocumentHeaderNodeWidget( - key: context.node.key, - node: context.node, - editorState: context.editorState, - ); - } - - @override - NodeValidator get nodeValidator => (_) => true; -} - class DocumentHeaderNodeWidget extends StatefulWidget { const DocumentHeaderNodeWidget({ required this.node, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_popover.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_popover.dart index 0216a8f56e..d24e0682b1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_popover.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_popover.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_menu.dart index 2ff4b2b1b7..9aa4523df8 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_menu.dart @@ -1,5 +1,5 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart new file mode 100644 index 0000000000..0dc30af324 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart @@ -0,0 +1,173 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/text_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_math_fork/flutter_math.dart'; +import 'package:provider/provider.dart'; + +class InlineMathEquationKeys { + const InlineMathEquationKeys._(); + + static const formula = 'formula'; +} + +class InlineMathEquation extends StatefulWidget { + const InlineMathEquation({ + super.key, + required this.formula, + required this.node, + required this.index, + this.textStyle, + }); + + final Node node; + final int index; + final String formula; + final TextStyle? textStyle; + + @override + State createState() => _InlineMathEquationState(); +} + +class _InlineMathEquationState extends State { + final popoverController = PopoverController(); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return _IgnoreParentPointer( + child: AppFlowyPopover( + controller: popoverController, + direction: PopoverDirection.bottomWithLeftAligned, + popupBuilder: (_) { + return MathInputTextField( + initialText: widget.formula, + onSubmit: (value) async { + popoverController.close(); + if (value == widget.formula) { + return; + } + final editorState = context.read(); + final transaction = editorState.transaction + ..formatText(widget.node, widget.index, 1, { + InlineMathEquationKeys.formula: value, + }); + await editorState.apply(transaction); + }, + ); + }, + offset: const Offset(0, 10), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const HSpace(2), + Math.tex( + widget.formula, + options: MathOptions( + style: MathStyle.text, + mathFontOptions: const FontOptions( + fontShape: FontStyle.italic, + ), + fontSize: 14.0, + color: widget.textStyle?.color ?? + theme.colorScheme.onBackground, + ), + ), + const HSpace(2), + ], + ), + ), + ), + ), + ); + } +} + +class MathInputTextField extends StatefulWidget { + const MathInputTextField({ + super.key, + required this.initialText, + required this.onSubmit, + }); + + final String initialText; + final void Function(String value) onSubmit; + + @override + State createState() => _MathInputTextFieldState(); +} + +class _MathInputTextFieldState extends State { + late final TextEditingController textEditingController; + + @override + void initState() { + super.initState(); + + textEditingController = TextEditingController( + text: widget.initialText, + ); + textEditingController.selection = TextSelection( + baseOffset: 0, + extentOffset: widget.initialText.length, + ); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 240, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: FlowyFormTextInput( + autoFocus: true, + textAlign: TextAlign.left, + controller: textEditingController, + contentPadding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 4.0, + ), + onEditingComplete: () => + widget.onSubmit(textEditingController.text), + ), + ), + const HSpace(4.0), + FlowyButton( + text: FlowyText(LocaleKeys.button_Done.tr()), + useIntrinsicWidth: true, + onTap: () => widget.onSubmit(textEditingController.text), + ), + ], + ), + ); + } +} + +class _IgnoreParentPointer extends StatelessWidget { + const _IgnoreParentPointer({ + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () {}, + onTapDown: (_) {}, + onDoubleTap: () {}, + onLongPress: () {}, + child: child, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart new file mode 100644 index 0000000000..7df4d49385 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart @@ -0,0 +1,52 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/image.dart'; +import 'package:flutter/material.dart'; + +final ToolbarItem inlineMathEquationItem = ToolbarItem( + id: 'editor.inline_math_equation', + group: 2, + isActive: onlyShowInSingleSelectionAndTextType, + builder: (context, editorState) { + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighlight = nodes.allSatisfyInSelection(selection, (delta) { + return delta.everyAttributes( + (attributes) => attributes[InlineMathEquationKeys.formula] != null, + ); + }); + return IconItemWidget( + iconBuilder: (_) => svgWidget( + 'editor/math', + size: const Size.square(16), + color: Colors.white, + ), + isHighlight: isHighlight, + tooltip: LocaleKeys.document_plugins_createInlineMathEquation.tr(), + onPressed: () async { + final selection = editorState.selection; + if (selection == null || selection.isCollapsed) { + return; + } + final node = editorState.getNodeAtPath(selection.start.path); + if (node == null) { + return; + } + final text = editorState.getTextInSelection(selection).join(); + final transaction = editorState.transaction + ..replaceText( + node, + selection.startIndex, + selection.length, + '\$', + attributes: { + InlineMathEquationKeys.formula: text, + }, + ); + await editorState.apply(transaction); + }, + ); + }, +); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart index 16e0a12e2d..acbc472e86 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart @@ -24,8 +24,6 @@ class MentionBlockKeys { static const mention = 'mention'; static const type = 'type'; // MentionType, String static const pageId = 'page_id'; - static const pageType = 'page_type'; - static const pageName = 'page_name'; } class InlinePageReferenceService { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart index 6c3d054a98..9bb56f873a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart @@ -1,22 +1,24 @@ +export 'actions/block_action_list.dart'; +export 'actions/option_action.dart'; export 'callout/callout_block_component.dart'; export 'code_block/code_block_component.dart'; export 'code_block/code_block_shortcut_event.dart'; -export 'header/cover_editor_bloc.dart'; -export 'header/document_header_node_widget.dart'; -export 'header/custom_cover_picker.dart'; -export 'emoji_picker/emoji_menu_item.dart'; -export 'extensions/flowy_tint_extension.dart'; +export 'database/database_view_block_component.dart'; export 'database/inline_database_menu_item.dart'; export 'database/referenced_database_menu_item.dart'; -export 'database/database_view_block_component.dart'; +export 'emoji_picker/emoji_menu_item.dart'; +export 'extensions/flowy_tint_extension.dart'; +export 'header/cover_editor_bloc.dart'; +export 'header/custom_cover_picker.dart'; +export 'header/document_header_node_widget.dart'; +export 'image/image_menu.dart'; +export 'image/image_selection_menu.dart'; +export 'inline_math_equation/inline_math_equation.dart'; +export 'inline_math_equation/inline_math_equation_toolbar_item.dart'; export 'math_equation/math_equation_block_component.dart'; export 'openai/widgets/auto_completion_node_widget.dart'; export 'openai/widgets/smart_edit_node_widget.dart'; export 'openai/widgets/smart_edit_toolbar_item.dart'; +export 'outline/outline_block_component.dart'; export 'toggle/toggle_block_component.dart'; export 'toggle/toggle_block_shortcut_event.dart'; -export 'outline/outline_block_component.dart'; -export 'image/image_menu.dart'; -export 'image/image_selection_menu.dart'; -export 'actions/option_action.dart'; -export 'actions/block_action_list.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart index a52589c735..12895a3e1d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart @@ -1,7 +1,8 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg, Log; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -173,13 +174,17 @@ class EditorStyleCustomizer { } InlineSpan customizeAttributeDecorator( - TextInsert textInsert, + Node node, + int index, + TextInsert text, TextSpan textSpan, ) { - final attributes = textInsert.attributes; + final attributes = text.attributes; if (attributes == null) { return textSpan; } + + // customize the inline mention block, like inline page final mention = attributes[MentionBlockKeys.mention] as Map?; if (mention != null) { final type = mention[MentionBlockKeys.type]; @@ -193,6 +198,21 @@ class EditorStyleCustomizer { ); } } + + // customize the inline math equation block + final formula = attributes[InlineMathEquationKeys.formula] as String?; + if (formula != null) { + return WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: InlineMathEquation( + node: node, + index: index, + formula: formula, + textStyle: style().textStyleConfiguration.text, + ), + ); + } + return textSpan; } } diff --git a/frontend/appflowy_flutter/lib/startup/startup.dart b/frontend/appflowy_flutter/lib/startup/startup.dart index 877345123b..e2a0e76d6d 100644 --- a/frontend/appflowy_flutter/lib/startup/startup.dart +++ b/frontend/appflowy_flutter/lib/startup/startup.dart @@ -46,8 +46,9 @@ class FlowyRunner { final launcher = getIt(); launcher.addTasks( [ - // handle platform errors. - const PlatformErrorCatcherTask(), + // this task should be first task, for handling platform errors. + // don't catch errors in test mode + if (!mode.isUnitTest) const PlatformErrorCatcherTask(), // localization const InitLocalizationTask(), // init the app window diff --git a/frontend/appflowy_flutter/lib/startup/tasks/windows.dart b/frontend/appflowy_flutter/lib/startup/tasks/windows.dart index c2d044e631..e3fa0a189b 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/windows.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/windows.dart @@ -19,17 +19,14 @@ class InitAppWindowTask extends LaunchTask with WindowListener { @override Future initialize(LaunchContext context) async { // Don't initialize on mobile or web. - if (!defaultTargetPlatform.isDesktop) { + if (!defaultTargetPlatform.isDesktop || context.env.isIntegrationTest) { return; } await windowManager.ensureInitialized(); windowManager.addListener(this); - Size windowSize = await WindowSizeManager().getSize(); - if (context.env.isIntegrationTest) { - windowSize = const Size(1600, 1200); - } + final windowSize = await WindowSizeManager().getSize(); final windowOptions = WindowOptions( size: windowSize, diff --git a/frontend/appflowy_flutter/lib/user/presentation/splash_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/splash_screen.dart index f586ce4005..1c33008b34 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/splash_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/splash_screen.dart @@ -96,7 +96,7 @@ class SplashScreen extends StatelessWidget { } Future _registerIfNeeded() async { - final result = await UserEventCheckUser().send(); + final result = await UserEventGetUserProfile().send(); if (!result.isLeft()) { await getIt().signUpAsGuest(); } diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/application_data_storage.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/application_data_storage.dart index 39f95695ae..41963bdc34 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/application_data_storage.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/application_data_storage.dart @@ -98,6 +98,7 @@ class MockApplicationDataStorage extends ApplicationDataStorage { final path = initialPath; if (path != null) { initialPath = null; + await super.setPath(path); return Future.value(path); } return super.getPath(); diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/image.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/image.dart index 6a0c3b8d3b..6e4dd11943 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/image.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/image.dart @@ -5,16 +5,23 @@ import 'package:flutter_svg/flutter_svg.dart'; /// /// Get the hover color from ThemeData class FlowySvg extends StatelessWidget { - const FlowySvg({super.key, this.size, required this.name}); + const FlowySvg({ + super.key, + required this.name, + this.size, + this.color, + }); + final String name; final Size? size; + final Color? color; @override Widget build(BuildContext context) { return svgWidget( name, size: size, - color: Theme.of(context).iconTheme.color, + color: color ?? Theme.of(context).iconTheme.color, ); } } diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 684f8c01b1..39c56c42a2 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -53,11 +53,11 @@ packages: dependency: "direct main" description: path: "." - ref: "572a174" - resolved-ref: "572a174892267e2f78f9c3d7f1fe4ca71c9be0db" + ref: c5b5e64 + resolved-ref: c5b5e641fe11ae634f02db112e71f40a119e9c44 url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git - version: "1.0.4" + version: "1.1.0" appflowy_popover: dependency: "direct main" description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 2cda3a2c96..2962dced30 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -46,7 +46,7 @@ dependencies: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: 572a174 + ref: c5b5e64 appflowy_popover: path: packages/appflowy_popover diff --git a/frontend/appflowy_tauri/src-tauri/src/main.rs b/frontend/appflowy_tauri/src-tauri/src/main.rs index ecee588521..edc59ed240 100644 --- a/frontend/appflowy_tauri/src-tauri/src/main.rs +++ b/frontend/appflowy_tauri/src-tauri/src/main.rs @@ -7,7 +7,7 @@ mod init; mod notification; mod request; -use flowy_notification::register_notification_sender; +use flowy_notification::{register_notification_sender, unregister_all_notification_sender}; use init::*; use notification::*; use request::*; @@ -22,6 +22,8 @@ fn main() { .on_menu_event(|_menu| {}) .on_page_load(|window, _payload| { let app_handler = window.app_handle(); + // Make sure hot reload won't register the notification sender twice + unregister_all_notification_sender(); register_notification_sender(TSNotificationSender::new(app_handler.clone())); // tauri::async_runtime::spawn(async move {}); window.listen_global(AF_EVENT, move |event| { diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 8d9ebca719..a97f55a076 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -85,6 +85,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "appflowy-integrate" version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc" dependencies = [ "anyhow", "collab", @@ -896,6 +897,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc" dependencies = [ "anyhow", "bytes", @@ -913,6 +915,7 @@ dependencies = [ [[package]] name = "collab-client-ws" version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc" dependencies = [ "bytes", "collab-sync", @@ -930,6 +933,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc" dependencies = [ "anyhow", "async-trait", @@ -956,6 +960,7 @@ dependencies = [ [[package]] name = "collab-derive" version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc" dependencies = [ "proc-macro2", "quote", @@ -967,6 +972,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc" dependencies = [ "anyhow", "collab", @@ -985,6 +991,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc" dependencies = [ "anyhow", "chrono", @@ -1004,6 +1011,7 @@ dependencies = [ [[package]] name = "collab-persistence" version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc" dependencies = [ "bincode", "chrono", @@ -1023,6 +1031,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc" dependencies = [ "anyhow", "async-trait", @@ -1056,6 +1065,7 @@ dependencies = [ [[package]] name = "collab-sync" version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc" dependencies = [ "bytes", "collab", diff --git a/frontend/rust-lib/dart-ffi/src/lib.rs b/frontend/rust-lib/dart-ffi/src/lib.rs index 45e201c228..b249a2756c 100644 --- a/frontend/rust-lib/dart-ffi/src/lib.rs +++ b/frontend/rust-lib/dart-ffi/src/lib.rs @@ -6,7 +6,7 @@ use lazy_static::lazy_static; use parking_lot::RwLock; use flowy_core::*; -use flowy_notification::register_notification_sender; +use flowy_notification::{register_notification_sender, unregister_all_notification_sender}; use lib_dispatch::prelude::ToBytes; use lib_dispatch::prelude::*; @@ -90,6 +90,8 @@ pub extern "C" fn sync_event(input: *const u8, len: usize) -> *const u8 { #[no_mangle] pub extern "C" fn set_stream_port(port: i64) -> i32 { + // Make sure hot reload won't register the notification sender twice + unregister_all_notification_sender(); register_notification_sender(DartNotificationSender::new(port)); 0 } diff --git a/frontend/rust-lib/flowy-notification/src/lib.rs b/frontend/rust-lib/flowy-notification/src/lib.rs index 5ce28e68c9..9d425f968c 100644 --- a/frontend/rust-lib/flowy-notification/src/lib.rs +++ b/frontend/rust-lib/flowy-notification/src/lib.rs @@ -14,6 +14,10 @@ lazy_static! { static ref NOTIFICATION_SENDER: RwLock>> = RwLock::new(vec![]); } +/// Register a notification sender. The sender will be alive until the process exits. +/// Flutter integration test or Tauri hot reload might cause register multiple times. +/// So before register a new sender, you might need to unregister the old one. Currently, +/// Just remove all senders by calling `unregister_all_notification_sender`. pub fn register_notification_sender(sender: T) { let box_sender = Box::new(sender); match NOTIFICATION_SENDER.write() { @@ -22,6 +26,13 @@ pub fn register_notification_sender(sender: T) { } } +pub fn unregister_all_notification_sender() { + match NOTIFICATION_SENDER.write() { + Ok(mut write_guard) => write_guard.clear(), + Err(err) => tracing::error!("Failed to remove all notification senders: {:?}", err), + } +} + pub trait NotificationSender: Send + Sync + 'static { fn send_subject(&self, subject: SubscribeObject) -> Result<(), String>; } diff --git a/frontend/scripts/code_generation/freezed/generate_freezed.sh b/frontend/scripts/code_generation/freezed/generate_freezed.sh old mode 100644 new mode 100755 index f4c84d910e..01f578d3dc --- a/frontend/scripts/code_generation/freezed/generate_freezed.sh +++ b/frontend/scripts/code_generation/freezed/generate_freezed.sh @@ -10,12 +10,14 @@ cd ../../../appflowy_flutter # Navigate to the appflowy_flutter directory and generate files echo "Generating files for appflowy_flutter" -flutter clean >/dev/null 2>&1 && flutter packages pub get >/dev/null 2>&1 && dart run build_runner clean && dart run build_runner build -d +# flutter clean >/dev/null 2>&1 && flutter packages pub get >/dev/null 2>&1 && dart run build_runner clean && +flutter packages pub get >/dev/null 2>&1 +dart run build_runner build -d echo "Done generating files for appflowy_flutter" echo "Generating files for packages" cd packages -for d in */ ; do +for d in */; do # Navigate into the subdirectory cd "$d" @@ -23,7 +25,8 @@ for d in */ ; do if [ -f "pubspec.yaml" ]; then echo "Generating freezed files in $d..." echo "Please wait while we clean the project and fetch the dependencies." - flutter clean >/dev/null 2>&1 && flutter packages pub get >/dev/null 2>&1 && dart run build_runner clean && dart run build_runner build -d + flutter packages pub get >/dev/null 2>&1 + dart run build_runner build -d echo "Done running build command in $d" else echo "No pubspec.yaml found in $d, it can\'t be a Dart project. Skipping." @@ -34,4 +37,4 @@ for d in */ ; do done # Return to the original directory -cd "$original_dir" \ No newline at end of file +cd "$original_dir" diff --git a/frontend/scripts/code_generation/generate.sh b/frontend/scripts/code_generation/generate.sh old mode 100644 new mode 100755 diff --git a/frontend/scripts/code_generation/language_files/generate_language_files.cmd b/frontend/scripts/code_generation/language_files/generate_language_files.cmd index 984f4f365d..146e8188e5 100644 --- a/frontend/scripts/code_generation/language_files/generate_language_files.cmd +++ b/frontend/scripts/code_generation/language_files/generate_language_files.cmd @@ -10,8 +10,6 @@ cd /d "%~dp0" cd ..\..\..\appflowy_flutter -call flutter clean - call flutter packages pub get echo Specifying source directory for AppFlowy Localizations. diff --git a/frontend/scripts/code_generation/language_files/generate_language_files.sh b/frontend/scripts/code_generation/language_files/generate_language_files.sh old mode 100644 new mode 100755 index 41e37eb268..dc624eb889 --- a/frontend/scripts/code_generation/language_files/generate_language_files.sh +++ b/frontend/scripts/code_generation/language_files/generate_language_files.sh @@ -10,8 +10,6 @@ cd "$(dirname "$0")" # Navigate to the project root cd ../../../appflowy_flutter -flutter clean - flutter packages pub get echo "Specifying source directory for AppFlowy Localizations." diff --git a/frontend/scripts/makefile/flutter.toml b/frontend/scripts/makefile/flutter.toml index 135fbaa66d..a2e3646536 100644 --- a/frontend/scripts/makefile/flutter.toml +++ b/frontend/scripts/makefile/flutter.toml @@ -167,7 +167,6 @@ script = [ [tasks.flutter-build] script = [""" cd appflowy_flutter/ - flutter clean flutter pub get flutter build ${TARGET_OS} --${BUILD_FLAG} """] @@ -176,7 +175,6 @@ script_runner = "@shell" [tasks.flutter-build.windows] script = [""" cd appflowy_flutter - exec cmd.exe /c flutter clean exec cmd.exe /c flutter pub get exec cmd.exe /c flutter build ${TARGET_OS} --${BUILD_FLAG} --build-name=${APP_VERSION} """] @@ -186,16 +184,15 @@ script_runner = "@duckscript" script_runner = "@shell" script = [ """ - chmod +x scripts/code_generation/generate.sh - """, - "scripts/code_generation/generate.sh" + sh scripts/code_generation/generate.sh + """ ] [tasks.code_generation.windows] script_runner = "@duckscript" script = [ """ - exec "scripts/code_generation/generate.cmd" + exec scripts/code_generation/generate.cmd """, ]