Compare commits

...
186 Commits
Author SHA1 Message Date
nathan bc70b04110 chore: update changelog 2022-11-28 19:52:26 +08:00
Lucas.Xu 11a0ef319f Merge pull request #1461 from ramanverma2k/feat_1305
ci: add .exe installer support for Windows releases #1305
2022-11-28 19:51:05 +08:00
Nathan.foooandnathan bd32ce5543 Feat: support single select option filter (#1494)
* feat: support select option filter

* chore: select option filter ui

* chore: support edit single select filter

* chore: add flutter tests

Co-authored-by: nathan <nathan@appflowy.io>
2022-11-28 19:41:57 +08:00
Nathan.foooandnathan c47f755155 feat: support checkbox filter (#1492)
* feat: support checkbox filter

* fix: unit test

Co-authored-by: nathan <nathan@appflowy.io>
2022-11-27 23:44:23 +08:00
Richard Shiue d6cbbf3c2f chore: date cell & header editor UI + move to theme.of(context).texttheme (#1473)
* chore: textstyles

* chore: sizing
2022-11-27 17:24:04 +08:00
Nathan.fooo 8c7e2d341d Merge pull request #1490 from AppFlowy-IO/filter_bloc_test
chore: add filter test
2022-11-27 17:20:24 +08:00
Nathan.foooandnathan 039d62cbbb chore: update version number (#1491)
Co-authored-by: nathan <nathan@appflowy.io>
2022-11-27 17:20:08 +08:00
nathan b6773a732b chore: add filter tests 2022-11-27 16:53:42 +08:00
nathan 60267e674e chore: refactor grid test directory 2022-11-27 15:38:02 +08:00
Nathan.foooandnathan 182bfae5ad fix: filter UI bugs (#1489)
* chore: remove the add filter button if there is no filters can not be added

* fix: update field info after filter was changed

* chore: update filter choicechip ui

* chore: insert and delete one by one to keep the delete/insert index is right

* chore: show filter after creating the default filter

* chore: update textfield_tags version to calm the warnings

* chore: try to fix potential fails on backend test

Co-authored-by: nathan <nathan@appflowy.io>
2022-11-27 14:47:11 +08:00
Nathan.foooandnathan 149c2a2725 Ci/build scripts (#1487)
* chore: rename flowy_dev name

* chore: rename tasks

Co-authored-by: nathan <nathan@appflowy.io>
2022-11-26 22:18:39 +08:00
Nathan.foooandnathan 69a7ae5201 Implement Grid's filter UI (#1474)
* fix: border of field cell

* chore: add filter button

* chore: refactor setting button event

* chore: notify row did changed with filter configuration

* chore: add filter bloc test

* chore: config add filter button

* chore: create filter

* chore: load filters and update corresponding field property

* chore: add filter choice chip

* chore: config choice chip ui

* chore: send notification when filter updated

* chore: update filter after update field type option data

* fix: remove/add filter when update field's type option

* chore: create home setting bloc to save the setting of the home screen

* chore: add filter test

* chore: edit text filter ui

* fix: filter cell bugs

* fix: insert row out of bound

* chore: update setting icon in grid

* chore: shrink wrap the filter list

* refactor: extract row container from row cache

* chore: disable split-debuginfo

Co-authored-by: nathan <nathan@appflowy.io>
2022-11-26 21:28:08 +08:00
Lucas.Xu 462daee934 fix: #1467 Editor's left and right margins is too rigid (#1471) 2022-11-22 20:06:54 +08:00
Richard Shiue 63059ea39d chore: tints depend on light and dark theme (#1470) 2022-11-22 20:06:32 +08:00
Richard Shiue 5a8f1db5a6 fix: divider colors for top and bottom border widgets (#1468) 2022-11-22 20:01:16 +08:00
Richard Shiue a8dc55b4f3 chore: use theme.of(context) text theme in settings and around app (#1466)
* chore: use theme.of(context) text theme in settings and around app

* chore: add textDecoration to FlowyText
2022-11-22 20:00:21 +08:00
Richard Shiue 67a253c9c7 fix: fix colors for buttons and allow more customization (#1465) 2022-11-21 08:23:37 +08:00
Richard Shiue dcf6628aa3 feat: add divider between sidebar and the page (#1450) 2022-11-19 19:05:09 +08:00
Richard Shiue dc462b3847 chore: use theme.of(context) text theme in shared widgets (#1460) 2022-11-19 18:00:08 +08:00
Richard Shiue 81bc31d1b1 chore: use theme.of(context) text theme in FlowyText (#1463) 2022-11-19 17:53:30 +08:00
Annie 500d5efc33 Merge pull request #1442 from ramanverma2k/fix_1416
fix: Application name in Windows taskbar.
2022-11-18 10:57:22 +08:00
Lucas.Xu 39254b7f17 Merge pull request #1319 from LucasXu0/image_selection_area
update image node selection style and fix the context menu display problem
2022-11-18 09:35:06 +08:00
Lucas.Xu 9c40a501fe Merge remote-tracking branch 'origin/main' into image_selection_area 2022-11-18 09:19:49 +08:00
ramanverma2k c1e6f27020 chore: update FileDescription to app name. 2022-11-17 19:45:25 +05:30
Richard Shiue d7f14db6ab chore: use theme.of(context) text theme in board (#1453) 2022-11-17 16:48:48 +08:00
Nathan.foooandnathan fc10ee2d6b Fix filter test (#1459)
* chore: move grid_view_editor.rs to view_editor folder

* chore: hide invisible rows

* fix: lock issue

* fix: flutter test potential failed

* chore: separate group tests

Co-authored-by: nathan <nathan@appflowy.io>
2022-11-17 16:44:17 +08:00
ramanverma2k af9b4b3922 ci: build & upload installer package for windows 2022-11-17 13:33:19 +05:30
Richard Shiue f00a78746e chore: add textTheme to AppTheme (#1448)
* chore: add textTheme to AppTheme

* chore: extend scaled font size options

* chore: add text styles to extension and rename extension to AFThemeExtension

* chore: use 2021 material design text style tokens
2022-11-17 15:28:57 +08:00
Richard Shiue eb35fb25af feat: initial steps to allow changing the app font (#1433)
* feat: initial steps to allow customizing app font

* chore: remove unnecessary factory constructor
2022-11-16 14:40:30 +08:00
Nathan.fooo 276df8202a Merge pull request #1456 from AppFlowy-IO/filter_bloc
Add Filter bloc
2022-11-16 08:50:03 +08:00
nathan 0ee27097ce chore: add fitler bloc test 2022-11-15 23:17:01 +08:00
nathan f1ac38dd59 chore: add filter service 2022-11-15 13:04:30 +08:00
Richard Shiueandnathan 66ed712952 feat: Add new page icon and refactor (#1449)
* refactor: port to popover action list for add new page

* feat: add icons to menu items for add new page

* chore: change translation for Doc to Document

Co-authored-by: nathan <nathan@appflowy.io>
2022-11-15 11:45:23 +08:00
nathan 3dddd907b0 chore: create filter bloc 2022-11-15 11:12:30 +08:00
Nathan.fooo 92de27eae8 Merge pull request #1455 from AppFlowy-IO/rename_doc_folder
chore: rename doc folder to document
2022-11-15 11:10:57 +08:00
nathan 1ee1f9c775 chore: fix ref 2022-11-15 10:46:26 +08:00
Nathan.fooo 5d20e16dc5 Merge pull request #1454 from AppFlowy-IO/feat/select_option_filter_test
Feat/select option filter test
2022-11-15 10:44:06 +08:00
nathan 400220a312 chore: rename doc folder to document 2022-11-15 10:22:35 +08:00
nathan 6846bbf103 chore: fix test 2022-11-15 10:19:13 +08:00
appflowy ef3837f246 chore: add selection option fitler test 2022-11-14 19:40:45 +08:00
Nathan.fooo c3a41ba9ad Merge pull request #1452 from AppFlowy-IO/feat/filter_date
Feat/filter date
2022-11-14 17:38:43 +08:00
appflowy 18c209848b chore: add select option tests 2022-11-14 17:21:32 +08:00
Lucas.Xu e076c2be90 Merge pull request #1451 from richardshiue/flowy-editor-background
fix: use surface color of colorscheme as editor's background color
2022-11-14 16:51:24 +08:00
appflowy c80fa5da78 chore: add date filter tests 2022-11-14 16:33:24 +08:00
Richard Shiue aaadab434c fix: use surface color of colorscheme as editor's background color 2022-11-14 15:31:39 +08:00
appflowy 0e137f12f5 chore: date with utc 2022-11-14 12:03:22 +08:00
appflowy 6aba344583 chore: rename some pb structs 2022-11-14 09:59:23 +08:00
Nathan.foooandnathan a0a16cc493 feat: config grid filter in backend & add tests
* chore: add search crate

* chore: add task order test

* chore: enable timeout

* add task crate

* chore: run filter task

* chore: run filter task

* chore: filter rows

* chore: cache filter result

* chore: filter rows when open a grid

* chore: add tests

* test: add number filter test

* test: add checkbox fitler test

* chore: fix test

Co-authored-by: nathan <nathan@appflowy.io>
2022-11-13 22:23:57 +08:00
Onyedika Israel UkwuezeandLucas.Xu a1e0282df0 fix: NetworkType null safety issues (#1435)
* fix: Networktype null safety issues

Networktype returns nulls when the connectivity result is vpn resulting to null safety issues.
Implemented a case for when the connectivity result is vpn to resolve this issue.

* chore: update connectivity_plus_platform_interface ^1.2.2

* chore: update network state on Rust side

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2022-11-13 11:57:47 +08:00
ramanverma2k bb370b28b7 fix: Application name in Windows taskbar. 2022-11-12 12:14:08 +05:30
Nathan.fooo dd1dcba599 chore: return user setting (#1438) 2022-11-11 17:24:10 +08:00
Richard Shiue 5b4a93ee34 test: split test into widget and unit test (#1432) 2022-11-11 12:42:20 +08:00
Nathan.fooo f426745094 fix: await the cell dispose (#1437) 2022-11-11 12:41:53 +08:00
Nathan.fooo 840095d73c fix: create grid for each test (#1431) 2022-11-10 20:22:37 +08:00
Richard Shiueandappflowy bbd64fae81 refactor: appflowy theme system pt. 1 (#1407)
* refactor: port theme provider to bloc

* refactor: port from custom theme type enum to material design's brightness

* chore: add custom color extension to ThemeData

* refactor: use Theme.of(context) when trying to get theme colors

* refactor: toggle widget code refactor

* refactor: flowy hover style refactor

* refactor: flowy icon refactor

* fix: regression on sidebar tooltip text from #1210

* chore: read color from theme.of

* chore: quick access to custom color

* fix: dart test

* fix: scrollbar regression

* chore: fix flutter lint

* chore: fix grid bloc test

Co-authored-by: appflowy <annie@appflowy.io>
2022-11-10 14:22:18 +08:00
Annie eff1da2812 chore: include latest product screenshots 2022-11-09 17:08:58 +08:00
Lucas.Xu 65f677b277 Merge pull request #1429 from LucasXu0/refactor_appflowy_editor_example
Refactor appflowy editor example
2022-11-09 16:48:57 +08:00
Lucas.Xu b6ad0ba597 chore: update changelog 2022-11-09 16:06:55 +08:00
Lucas.Xu 9b56cbb648 fix: export error on Web platform 2022-11-09 16:00:07 +08:00
Lucas.Xu e20ce9052a feat: implement theme customizer showcase 2022-11-09 15:36:30 +08:00
Lucas.Xu 853be71bf5 feat: implement initalize editor from json and markdown 2022-11-09 15:06:36 +08:00
Lucas.Xu 9a908abfdd feat: implement save document to markdown 2022-11-09 14:36:55 +08:00
Nathan.fooo e482ac75f9 test: edit a field in kanban board (#1428) 2022-11-09 12:25:07 +08:00
Lucas.Xu 205b5f2c21 feat: implement save document to json 2022-11-09 11:50:08 +08:00
Lucas.Xu 064ed16a7a feat: implement drawer and simple editor 2022-11-09 11:37:26 +08:00
Lucas.Xu 8a53abef3f chore: update version and documentation 2022-11-09 11:37:06 +08:00
Nathan.fooo 42c2c4738a chore: remove flutter quill (#1426) 2022-11-09 11:07:29 +08:00
Lucas.Xu cdf6f1b38a Merge pull request #1424 from LucasXu0/markdown
Implement appflowy editor document to markdown
2022-11-09 09:50:15 +08:00
Lucas.Xu 17536fdecb feat: implement paste markdown text 2022-11-09 09:50:00 +08:00
Lucas.Xu eb7356474c feat: integate appflowy_editor markdown codec into appflowy 2022-11-08 21:43:51 +08:00
Lucas.Xu 76d1267aa5 feat: remove the codec code from main project 2022-11-08 21:36:51 +08:00
Lucas.Xu 888c1b86f0 test: markdown decoder test 2022-11-08 21:34:27 +08:00
Nathan.fooo 882d5535e6 Extract protobuf structs from flowy-sync crate (#1425)
* refactor: extract data model from flowy-sync crate

* chore: rename lib-infra features
2022-11-08 21:13:28 +08:00
Lucas.Xu 4622a412b7 feat: markdown to document 2022-11-08 20:10:09 +08:00
Lucas.Xu fc35f74751 feat: markdown to delta 2022-11-08 17:19:14 +08:00
Lucas.Xu f6e1f2185e chore: remove unused code 2022-11-08 15:55:02 +08:00
Lucas.Xu 2e7f803e02 test: markdown encoder test 2022-11-08 15:44:40 +08:00
Lucas.Xu 9eaa79b558 feat: document to markdown 2022-11-08 15:44:10 +08:00
Lucas.Xu c85ab276e9 feat: delta to markdown 2022-11-08 14:47:10 +08:00
Nathan.fooo 7f6ad504fd Merge pull request #1423 from AppFlowy-IO/refactor/revision_model
Refactor/revision model
2022-11-08 14:36:21 +08:00
appflowy 130c5f8e6e refactor: reanme flowy-grid-data-model to grid-rev-model 2022-11-08 14:12:13 +08:00
appflowy d39b31f357 refactor: rename folder-data-model to folder-rev-model 2022-11-08 13:47:48 +08:00
Nathan.fooo f36cc9a5d0 Merge pull request #1420 from AppFlowy-IO/refactor/flowy_revision_crate
Refactor: merge multiple revision into one
2022-11-08 13:41:04 +08:00
appflowy 6425997508 chore: merge lagging revisions when close the document 2022-11-08 12:00:04 +08:00
Lucas.Xu bf06211bff Merge pull request #1422 from LucasXu0/editor_background
chore: set theme.surface as editor's background color
2022-11-08 11:00:49 +08:00
Lucas.Xu 506f789318 chore: set theme.surface as editor's background color 2022-11-08 10:00:28 +08:00
nathan ebdd28cf1c chore: add ref count map 2022-11-08 09:30:10 +08:00
appflowy de4c1b24ef chore: fix warnings 2022-11-07 20:40:10 +08:00
Lucas.Xu ab664ebb2f Merge pull request #1419 from LucasXu0/fix_1413
fix: #1413 [Bug] Azrty keyboard not opening menu when typing /
2022-11-07 20:20:58 +08:00
Nathan.fooo 20f527f4eb Merge pull request #1384 from Cyrine-benabid/fix-option_button_doesnt-close_popover
Close popover on text field tap
2022-11-07 17:36:44 +08:00
appflowy b3b24d0cc0 chore: calculate the compact length after receiving ack 2022-11-07 17:30:24 +08:00
Lucas.Xu b15ed56008 fix: #1413 [Bug] Azrty keyboard not opening menu when typing / 2022-11-07 16:46:25 +08:00
Nathan.fooo 992be378bb Merge pull request #1418 from AppFlowy-IO/ci/fix_build
ci: fix typo
2022-11-07 15:54:33 +08:00
appflowy 5156248918 ci: fix typo 2022-11-07 15:27:31 +08:00
Lucas.Xu adaad9367d Merge pull request #1417 from LucasXu0/ci
fix: Windows CI error, need to install vcpkg
2022-11-07 15:08:54 +08:00
Lucas.Xu 8dc28e9286 fix: Windows CI error, need to install vcpkg 2022-11-07 14:49:32 +08:00
Lucas.Xu 49b2bcd20d Merge pull request #1386 from ramanverma2k/dep-pkg_1303
feat: create deb package for linux (#1303)
2022-11-07 10:29:38 +08:00
nathan e729c0a81f chore: add documentation 2022-11-07 10:09:05 +08:00
nathan ff7aab73cc chore: merge with config setting 2022-11-06 10:31:07 +08:00
Richard Shiue fcd214b1c0 fix: lint unhappy with unused import (#1412) 2022-11-04 22:15:03 +08:00
Xavier Chrétien a50f2a6eb9 ci: Add dmg support for MacOS and add the .dmg to the release (#1401) 2022-11-04 22:14:28 +08:00
nathan 2c71e4f885 chore: add tests 2022-11-02 17:15:27 +08:00
nathan f5dc9ed975 test: add revision tests 2022-11-02 11:49:36 +08:00
nathan 608a08eb76 refactor: md5 of revision 2022-11-02 10:21:10 +08:00
Eakam cd8e4ddf00 fix: disappearing hover effect on popover trigger (#1403) 2022-11-01 21:01:35 +08:00
cyrine-benabid 9c8d00bb61 fix: request focus when InputTextField is added (#1394)
* fix request focus when InputTextField is added

* style: fix indentation
2022-11-01 20:56:42 +08:00
ce19 9015e6f160 chore: Improved CHangelog.md (#1387) 2022-11-01 20:55:40 +08:00
wanderer163andarpitpandey0209 1262653144 fix: typos and grammar (#1408)
Co-authored-by: arpitpandey0209 <arpit100.700@rediffmail.com>
2022-11-01 20:51:03 +08:00
Nathan.fooo e9ad705ea3 Refactor/revision compose (#1410) 2022-11-01 18:59:53 +08:00
Nathan.foooandnathan b0d2cdf55a ci: Update release title (#1409)
* chore: update release title

* chore: update changelog

Co-authored-by: nathan <nathan@appflowy.io>
2022-11-01 14:27:33 +08:00
Cyrine-benabid c7d8a0b7c3 refactor: remove useless popoverMutex from _AddOptionButton widget 2022-10-31 06:48:28 +01:00
Cyrine-benabid 286c89bf74 refactor: remove useless onTap and don't pass popovermutex when not needed 2022-10-30 20:30:38 +01:00
Cyrine-benabid 339e3d242f style: rename class and fix constructor 2022-10-30 20:20:13 +01:00
Cyrine-benabid fad02eb09d fix: remove useless onTap 2022-10-30 20:19:20 +01:00
Cyrine-benabid 8f1752a253 fix: add missing super.initState() call 2022-10-30 19:30:56 +01:00
Cyrine-benabid 16a8dbc6ce feat: unfocus CreateOptionTextField when popover state changes 2022-10-30 18:27:12 +01:00
Cyrine-benabid 1a53a0e375 refactor: implementation using focusNode 2022-10-30 18:25:50 +01:00
appflowy 52b713081d chore: update version 2022-10-30 19:44:39 +08:00
Nathan.fooo 3fbcd928db fix: padding for edit field (#1400) 2022-10-30 19:31:53 +08:00
Lucas.Xu fa4e3d0d2a Merge pull request #1397 from LucasXu0/auto_focus
feat: implement auto focus
2022-10-30 17:07:35 +08:00
ramanverma2k 319c358d1b ci: add postinst & postrm scripts to linux deb package 2022-10-30 12:09:20 +05:30
Nathan.fooo bc5548ff75 fix: changeset composing (#1398) 2022-10-30 12:54:07 +08:00
ramanverma2k 46a20123c1 ci: update linux deb package build workflow 2022-10-30 09:45:52 +05:30
ramanverma2k 380d921dae ci: respect debian package naming convention. 2022-10-30 09:33:06 +05:30
Lucas.Xu 6130dfe36a feat: implement auto focus 2022-10-30 10:34:55 +08:00
Lucas.Xu 64b4a99aa1 Merge pull request #1391 from LucasXu0/pick_0_0_6_1_to_main
chore: pick release/0.0.6 to main
2022-10-30 10:17:05 +08:00
Nathan.foooandnathan 9344ea23ca fix: support float value in attributes (#1396)
Co-authored-by: nathan <nathan@appflowy.io>
2022-10-30 09:35:15 +08:00
Cyrine-benabid baeedf557d feat: close popover on AddOptionButton tap 2022-10-30 02:24:57 +02:00
Cyrine ben-abid b7f369b3d9 feat: close popover on text field tap 2022-10-29 16:47:32 +02:00
Cyrine ben-abid 3e76fa8e42 fix: close popover on add option button clicked and textfield focused 2022-10-29 16:47:32 +02:00
Nathan.fooo 783fd40f63 Feat/op compose (#1392) 2022-10-29 20:54:11 +08:00
Lucas.Xu 10541502b8 chore: pick release/0.0.6 to main 2022-10-29 17:00:40 +08:00
Lucas.Xu 95fdfd7da2 Merge pull request #1390 from LucasXu0/fix_enter_key_handler
fix: remove the unnecessary operation in enter key handler
2022-10-29 16:56:07 +08:00
Lucas.Xu 4a48a2af5a fix: remove the unnecessary operation in enter keyhandler 2022-10-29 15:16:31 +08:00
Annie d82a6c5924 Merge pull request #1388 from Abubakrce19/chore-improved-changelog.md
chore: improved changelog.md
2022-10-29 15:00:41 +08:00
Sean Riley Hawkins 4d89b7a24e test: scroll service test for editor 2022-10-28 21:25:04 +08:00
ce19 e5bca9e9cc chore: improved changelog.md 2022-10-28 11:07:22 +05:00
ramanverma2k 2cbd74c716 feat: create deb package for linux 2022-10-27 20:05:37 +05:30
Lucas.Xu 43e4b1efad Merge pull request #1382 from LucasXu0/changelog
chore: update CHANGELOG
2022-10-27 20:32:35 +08:00
Lucas.Xu a8f791e98c chore: update CHANGELOG 2022-10-27 15:38:37 +08:00
Lucas.Xu b6dc18ba32 chore: update appflowy_editor CHANGELOG 2022-10-27 15:30:19 +08:00
Lucas.Xu 9c24597bbd Merge pull request #1380 from LucasXu0/appflowy_editor_0_0_7
appflowy editor 0.0.7
2022-10-27 15:23:17 +08:00
Lucas.Xu ea46272b49 Merge pull request #1373 from LucasXu0/fix_checkbox_copy
fix: copy link and code style
2022-10-27 15:22:54 +08:00
Lucas.Xu ac36d76eed chore: update version 0.0.7 2022-10-27 14:53:15 +08:00
Lucas.Xu 90b9456b0b chore: update documentation for customizing a theme 2022-10-27 14:52:17 +08:00
Nathan.fooo 309bbbd8e7 chore: Add group by field tests 2022-10-27 14:11:15 +08:00
Lucas.Xu 862ba3173a chore: update main.dart 2022-10-27 14:08:54 +08:00
Lucas.Xu 76cdb689fb chore: update language for bn_BN 2022-10-27 14:00:24 +08:00
Nathan.fooo 3bbf91ab2b Add board group test (#1376) 2022-10-26 22:36:34 +08:00
Nathan.fooo c8044a92d1 fix: group by field (#1374) 2022-10-26 21:46:47 +08:00
Lucas.Xu 799ceb432f fix: copy link and code style 2022-10-26 20:14:35 +08:00
Lucas.Xu c7c9048fe3 Merge pull request #1364 from LucasXu0/adapt_dart_mode
feat: adapt dark mode
2022-10-26 14:55:37 +08:00
Utsav Paul 7e2a0144a1 feat: added new translation (bn_BN) to appflowy-editor (#1356)
* Create intl_bn_BN.arb

* Update intl_bn_BN.arb
2022-10-26 14:52:53 +08:00
Lucas.Xu 3f97820094 feat: customize the placeholder text style 2022-10-26 14:51:39 +08:00
Lucas.Xu e7adc3bcbf fix: selection menu show in wrong position 2022-10-26 14:36:00 +08:00
Lucas.Xu 381913307c fix: copy selection exception 2022-10-26 11:54:21 +08:00
Lucas.Xu 799ed2fdb9 feat: custom upload image menu style 2022-10-26 11:47:38 +08:00
Lucas.Xu a68903918c feat: custom link menu style 2022-10-26 11:43:10 +08:00
Nathan.fooo 170bbb6db0 test: add home bloc test (#1371) 2022-10-26 11:09:51 +08:00
Lucas.Xu a702c06dc8 feat: custom context menu style 2022-10-26 11:01:25 +08:00
Lucas.Xu fac76ac5b8 feat: custom selection menu style 2022-10-26 10:52:30 +08:00
Nathan.fooo 2eea57aefa test: add app bloc test (#1370) 2022-10-26 10:38:57 +08:00
Lucas.Xu 23a65bfa2a feat: customize appflowy editor selection menu style 2022-10-26 10:16:14 +08:00
Hridaya Agrawal 23ffc3d0a8 chore: Update README.md (#1346) 2022-10-26 08:28:18 +08:00
Utsav PaulandNathan.fooo ff5a8c322d chore: Update README.md (#1357)
Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
2022-10-26 08:26:00 +08:00
Taduri Saimahesh e320439049 chore: Update README.md (#1360) 2022-10-26 08:23:58 +08:00
Lucas.Xu 8656cfeb1e chore: add extension for themedata 2022-10-25 22:15:31 +08:00
Lucas.Xu 6a853036a5 feat: customize appflowy editor heading style 2022-10-25 22:07:30 +08:00
Lucas.Xu c5cea81be2 feat: integrate appflowy editor dark mode 2022-10-25 21:58:44 +08:00
Lucas.Xu dde50d32d6 feat: refactor theme plugin, use themedata extension 2022-10-25 21:35:44 +08:00
Lucas.Xu cdee706f46 feat: refactor theme plugin, use themedata extension 2022-10-25 20:22:38 +08:00
Nathan.fooo 586c20a14e chore: add board test (#1363) 2022-10-25 19:50:11 +08:00
Richard Shiue 1e6c4a4d88 feat: introduce a default font (#1210)
* style: code auto-format

* feat: introduce default poppins font

* chore: port more const textstyles to shared styles
2022-10-25 19:49:58 +08:00
Nathan.fooo 18185cc90c chore: add app setting test (#1361) 2022-10-25 17:30:57 +08:00
Nathan.foooandnathan 67e4a759c7 chore: add reorder bloc test (#1354)
* chore: add reorder bloc test

* chore: add trash test

Co-authored-by: nathan <nathan@appflowy.io>
2022-10-25 16:51:51 +08:00
Lucas.Xu 68da3955c1 fix: disable toolbar hover color 2022-10-25 14:41:50 +08:00
Lucas.Xu 3fb997af84 feat: support dark mode for number-list and bulleted-list 2022-10-25 14:40:18 +08:00
Lucas.Xu 6fb677d346 Merge pull request #1353 from alemoreau/fix-undo_quote_doesnt_work
fix: remove attribute if it already exists on the node
2022-10-25 14:30:55 +08:00
Lucas.Xu 50814b2bee test: add test for format_rich_text_style.dart 2022-10-25 11:13:14 +08:00
Nathan.foooandnathan 5a6103f5d6 chore: add app bloc test (#1344)
* chore: add app bloc test

* chore: add wait

Co-authored-by: nathan <nathan@appflowy.io>
2022-10-25 09:01:26 +08:00
Richard Shiue da3d575295 chore: no longer need to delete all options before each test (#1348) 2022-10-25 08:36:09 +08:00
Richard Shiue 10229ca8b5 fix: avoid overlapping option colors (#1318) 2022-10-25 08:35:31 +08:00
Richard Shiue 58ad9a260c fix: several grid ui improvements (#1347) 2022-10-25 08:29:21 +08:00
Alexandre Moreau 5d2ff8e07c fix: remove attribute if it already exists on the node 2022-10-25 00:42:31 +02:00
Lucas.Xu 2d66dfabe8 fix: the old context menu didn't dismiss after double-tap the other place 2022-10-19 16:49:41 +08:00
Lucas.Xu 585e71aa9d feat: update image builder to support board selection style 2022-10-19 16:35:52 +08:00
679 changed files with 20213 additions and 57686 deletions

No files matched your search

@@ -4,10 +4,12 @@ on:
push:
branches:
- "main"
- "release/*"
pull_request:
branches:
- "main"
- "release/*"
paths:
- "frontend/app_flowy/packages/appflowy_editor/**"
+7 -3
View File
@@ -4,10 +4,12 @@ on:
push:
branches:
- "main"
- "release/*"
pull_request:
branches:
- "main"
- "release/*"
jobs:
build:
@@ -62,6 +64,8 @@ jobs:
sudo apt-get update
sudo apt-get install -y dart curl build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
sudo apt-get install keybinder-3.0
elif [ "$RUNNER_OS" == "Windows" ]; then
vcpkg integrate install
elif [ "$RUNNER_OS" == "macOS" ]; then
echo 'do nothing'
fi
@@ -74,10 +78,10 @@ jobs:
cargo install cargo-make
cargo install duckscript_cli
- name: Cargo make flowy_dev
- name: Cargo make appflowy-deps-tools
working-directory: frontend
run: |
cargo make flowy_dev
cargo make appflowy-deps-tools
- name: Config Flutter
run: |
@@ -85,7 +89,7 @@ jobs:
flutter config --enable-linux-desktop
elif [ "$RUNNER_OS" == "macOS" ]; then
flutter config --enable-macos-desktop
elif [ "$RUNNER_OS" == "windows" ]; then
elif [ "$RUNNER_OS" == "Windows" ]; then
flutter config --enable-windows-desktop
fi
shell: bash
+4 -2
View File
@@ -9,12 +9,14 @@ on:
push:
branches:
- "main"
- "release/*"
paths:
- "frontend/app_flowy/**"
pull_request:
branches:
- "main"
- "release/*"
paths:
- "frontend/app_flowy/**"
@@ -62,7 +64,7 @@ jobs:
- name: Cargo make flowy dev
working-directory: frontend
run: |
cargo make flowy_dev
cargo make appflowy-deps-tools
- name: Flutter Deps
run: flutter packages pub get
@@ -71,7 +73,7 @@ jobs:
- name: Build FlowySDK
working-directory: frontend
run: |
cargo make --profile development-linux-x86_64 flowy-sdk-dev
cargo make --profile development-linux-x86_64 appflowy-sdk-dev
- name: Flutter Code Generation
working-directory: frontend/app_flowy
+3 -1
View File
@@ -4,12 +4,14 @@ on:
push:
branches:
- "main"
- "release/*"
paths:
- "frontend/app_flowy/**"
pull_request:
branches:
- "main"
- "release/*"
paths:
- "frontend/app_flowy/**"
@@ -52,7 +54,7 @@ jobs:
working-directory: frontend
run: |
cargo install cargo-make
cargo make flowy_dev
cargo make appflowy-deps-tools
- name: Flutter Deps
working-directory: frontend/app_flowy
+103 -4
View File
@@ -28,7 +28,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
release_name: v${{ github.ref }}
body_path: ${{ env.RELEASE_NOTES_PATH }}
build-linux-x86:
@@ -37,6 +37,7 @@ jobs:
env:
LINUX_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/linux/Release
LINUX_ZIP_NAME: AppFlowy-linux-x86.tar.gz
LINUX_PACKAGE_NAME: AppFlowy_${{ github.ref_name }}_linux-amd64.deb
steps:
- name: Checkout
uses: actions/checkout@v2
@@ -62,7 +63,7 @@ jobs:
source $HOME/.cargo/env
cargo install --force cargo-make
cargo install --force duckscript_cli
cargo make flowy_dev
cargo make appflowy-deps-tools
- name: Build Linux app
working-directory: frontend
@@ -70,6 +71,46 @@ jobs:
flutter config --enable-linux-desktop
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86_64 appflowy
- name: Build Linux package
working-directory: ${{ env.LINUX_APP_RELEASE_PATH }}
run: |
mkdir -p package/opt && mv AppFlowy package/opt/
cd package && mkdir DEBIAN
# Create control file
printf 'Package: AppFlowy
Version: %s
Architecture: amd64
Essential: no
Priority: optional
Maintainer: AppFlowy
Description: An Open Source Alternative to Notion\n' "${{ github.ref_name }}" > DEBIAN/control
# postinst script for creating symlink
printf '#!/bin/bash
if [ -e /usr/local/bin/appflowy ]; then
echo "Symlink already exists, skipping."
else
echo "Creating Symlink in /usr/local/bin/appflowy"
ln -s /opt/AppFlowy/app_flowy /usr/local/bin/appflowy
fi' > DEBIAN/postinst
chmod 0755 DEBIAN/postinst
# postrm script for cleaning up residuals
printf '#!/bin/bash
if [ -e /usr/local/bin/appflowy ]; then
rm /usr/local/bin/appflowy
fi' > DEBIAN/postrm
chmod 0755 DEBIAN/postrm
mkdir -p usr/share/applications
# Update Exec & icon path in desktop entry
grep -rl "\[CHANGE_THIS\]" ./opt/AppFlowy/appflowy.desktop.temp | xargs sed -i "s/\[CHANGE_THIS\]/\/opt/"
# Add desktop entry in package
mv ./opt/AppFlowy/appflowy.desktop.temp ./usr/share/applications/appflowy.desktop
# Build
cd ../ && dpkg-deb --build --root-owner-group package ${{ env.LINUX_PACKAGE_NAME }}
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
@@ -81,12 +122,24 @@ jobs:
asset_name: ${{ env.LINUX_ZIP_NAME }}
asset_content_type: application/octet-stream
- name: Upload Release Asset Install Package
id: upload-release-asset-install-package
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ${{ env.LINUX_APP_RELEASE_PATH }}/${{ env.LINUX_PACKAGE_NAME }}
asset_name: ${{ env.LINUX_PACKAGE_NAME }}
asset_content_type: application/octet-stream
build-macos-x86_64:
runs-on: macos-latest
needs: create-release
env:
MACOS_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/macos/Release
MACOS_X86_ZIP_NAME: Appflowy-macos-x86_64.zip
MACOS_DMG_NAME: Appflowy-macos-x86_64-installer
steps:
- name: Checkout
uses: actions/checkout@v2
@@ -108,7 +161,7 @@ jobs:
source $HOME/.cargo/env
cargo install --force cargo-make
cargo install --force duckscript_cli
cargo make flowy_dev
cargo make appflowy-deps-tools
- name: Build macOS app for x86_64
working-directory: frontend
@@ -116,6 +169,21 @@ jobs:
flutter config --enable-macos-desktop
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86_64 appflowy
- name: Create MacOS dmg
working-directory: frontend
run: |
brew install create-dmg
create-dmg \
--volname ${{ env.MACOS_DMG_NAME }} \
--hide-extension "AppFlowy.app" \
--background scripts/dmg_assets/AppFlowyInstallerBackground.jpg \
--window-size 600 450 \
--icon-size 94 \
--icon "AppFlowy.app" 141 249 \
--app-drop-link 458 249 \
"${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" \
"${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app"
- name: Archive macOS app
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
run: zip --symlinks -qr ${{ env.MACOS_X86_ZIP_NAME }} AppFlowy.app
@@ -129,6 +197,15 @@ jobs:
asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_X86_ZIP_NAME }}
asset_name: ${{ env.MACOS_X86_ZIP_NAME }}
asset_content_type: application/octet-stream
- name: Upload DMG Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg
asset_name: ${{ env.MACOS_DMG_NAME }}.dmg
asset_content_type: application/octet-stream
build-windows-x86_64:
runs-on: windows-latest
@@ -136,6 +213,7 @@ jobs:
env:
WINDOWS_APP_RELEASE_PATH: frontend\app_flowy\product\${{ github.ref_name }}\windows
WINDOWS_ZIP_NAME: AppFlowy-windows-x86_64.zip
WINDOWS_INSTALLER_NAME: AppFlowy-${{ github.ref_name }}-windows-x86_64
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -157,7 +235,7 @@ jobs:
vcpkg integrate install
cargo install --force cargo-make
cargo install --force duckscript_cli
cargo make flowy_dev
cargo make appflowy-deps-tools
- name: Build Windows app
working-directory: frontend
@@ -169,6 +247,16 @@ jobs:
with:
files: ${{ env.WINDOWS_APP_RELEASE_PATH }}\
dest: ${{ env.WINDOWS_APP_RELEASE_PATH }}\${{ env.WINDOWS_ZIP_NAME }}
- name: Copy installer config & icon file
working-directory: frontend
run: |
cp scripts/windows_installer/* ../${{ env.WINDOWS_APP_RELEASE_PATH }}
- name: Build installer executable
working-directory: ${{ env.WINDOWS_APP_RELEASE_PATH }}
run: |
iscc /F${{ env.WINDOWS_INSTALLER_NAME }} inno_setup_config.iss /DAppVersion=${{ github.ref_name }}
- name: Upload Release Asset
id: upload-release-asset
@@ -180,3 +268,14 @@ jobs:
asset_path: ${{ env.WINDOWS_APP_RELEASE_PATH }}\${{ env.WINDOWS_ZIP_NAME }}
asset_name: ${{ env.WINDOWS_ZIP_NAME }}
asset_content_type: application/octet-stream
- name: Upload Installer Asset
id: upload-installer-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ${{ env.WINDOWS_APP_RELEASE_PATH }}\Output\${{ env.WINDOWS_INSTALLER_NAME }}.exe
asset_name: ${{ env.WINDOWS_INSTALLER_NAME }}.exe
asset_content_type: application/octet-stream
+2
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- "main"
- "release/*"
paths:
- "frontend/rust-lib/**"
- "shared-lib/**"
@@ -11,6 +12,7 @@ on:
pull_request:
branches:
- "main"
- "release/*"
paths:
- "frontend/rust-lib/**"
- "shared-lib/**"
+4 -2
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- "main"
- "release/*"
paths:
- "frontend/rust-lib/**"
- "shared-lib/**"
@@ -11,6 +12,7 @@ on:
pull_request:
branches:
- "main"
- "release/*"
paths:
- "frontend/rust-lib/**"
- "shared-lib/**"
@@ -37,12 +39,12 @@ jobs:
working-directory: frontend
run: |
cargo install cargo-make
cargo make flowy_dev
cargo make appflowy-deps-tools
- name: Build FlowySDK
working-directory: frontend
run: |
cargo make --profile development-linux-x86_64 flowy-sdk-dev
cargo make --profile development-linux-x86_64 appflowy-sdk-dev
- run: rustup component add rustfmt
working-directory: frontend/rust-lib
+2
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- "main"
- "release/*"
paths:
- "frontend/rust-lib/**"
- "shared-lib/**"
@@ -11,6 +12,7 @@ on:
pull_request:
branches:
- "main"
- "release/*"
paths:
- "frontend/rust-lib/**"
- "shared-lib/**"
+26 -13
View File
@@ -1,14 +1,27 @@
# Release Notes
## Version 0.0.7 - 11/27/2022
### New features
- Support adding filters by the text/checkbox/single-select property in Grid
## Version 0.0.6.2 - 10/30/2022
- Fix some bugs
## Version 0.0.6.1 - 10/26/2022
### New features
- Optimize appflowy_editor dark mode style
### Bug Fixes
- Unable to copy the text with checkbox or link style
## Version 0.0.6 - 10/23/2022
New features
### New features
- Integrate **appflowy_editor**
## Version 0.0.5.3 - 09/26/2022
New features
### New features
- Open the next page automatically after deleting the current page
- Refresh the Kanban board after altering a property type
@@ -22,7 +35,7 @@ New features
## Version 0.0.5.2 - 09/16/2022
New features
### New features
- Enable adding a new card to the "No Status" group
- Fix some bugs
@@ -33,19 +46,19 @@ New features
## Version 0.0.5.1 - 09/14/2022
New features
### New features
- Enable deleting a field in board
- Fix some bugs
## Version 0.0.5 - 09/08/2022
New Features - Kanban Board like Notion and Trello beta
### New features - Kanban Board like Notion and Trello beta
Boards are the best way to manage projects & tasks. Use them to group your databases by select, multiselect, and checkbox.
<p align="left"><img src="https://user-images.githubusercontent.com/12026239/190055984-6efa2d7a-ee38-4551-859e-ee56388e1859.gif" width="1000px" /></p>
- Set up columns that represent a specific phase of the project cycle and use cards to represent each project / task
- Drag and drop a card from one phase / column to another phase / column
- Set up columns that represent a specific phase of the project cycle and use cards to represent each project/task
- Drag and drop a card from one phase/column to another phase/column
- Update database properties in the Board view by clicking on a property and making edits on the card
### Other Features & Improvements
@@ -55,7 +68,7 @@ Boards are the best way to manage projects & tasks. Use them to group your datab
## Version 0.0.5 - beta.2 - beta.1 - 09/01/2022
New features
### New features
- Board-view database
- Support start editing after creating a new card
- Support editing the card directly by clicking the edit button
@@ -67,7 +80,7 @@ New features
## Version 0.0.5 - beta.1 - 08/25/2022
New features
### New features
- Board-view database
- Group by single select
- drag and drop cards
@@ -90,7 +103,7 @@ New features
## Version 0.0.4 - beta.3 - 05/02/2022
- Drag to reorder app/ view/ field
- Row record open as a page
- Row record opens as a page
- Auto resize the height of the row in the grid
- Support more number formats
- Search column options, supporting Single-select, Multi-select, and number format
@@ -114,7 +127,7 @@ New features
## Version 0.0.4 - beta.1 - 04/08/2022
v0.0.4 - beta.1 is pre-release
New features
### New features
- Table-view database
- supported column types: Text, Checkbox, Single-select, Multi-select, Numbers
- hide / delete columns
@@ -123,12 +136,12 @@ New features
## Version 0.0.3 - 02/23/2022
v0.0.3 is production ready, available on Linux, macOS, and Windows
New features
### New features
- Dark Mode
- Support new languages: French, Italian, Russian, Simplified Chinese, Spanish
- Add Settings: Toggle on Dark Mode; Select a language
- Show device info
- Add tooltip on toolbar icons
- Add tooltip on the toolbar icons
Bug fixes and improvements
- Increased height of action
+8 -4
View File
@@ -23,13 +23,15 @@ You are in charge of your data and customizations.
<a href="https://twitter.com/appflowy"><b>Twitter</b></a>
</p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/174754661-682980e4-e386-4685-bb6f-2da357390b61.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/200787830-96be260b-d0a0-4152-864e-6730b19095cd.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/174753177-98e4c899-2356-4137-bb42-374bba2b127b.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/190650183-a940f1e0-a2c5-4797-ab3a-56758f6f696c.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
## User Installation
Please view the [documentation](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods) for OS specific installation instructions.
* [Windows/Mac/Linux](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/mac-windows-linux-packages)
* [Docker](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/installing-with-docker)
* [Source](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/from-source)
## Built With
@@ -49,7 +51,9 @@ Please view the [documentation](https://appflowy.gitbook.io/docs/essential-docum
- [AppFlowy Roadmap ReadMe](https://appflowy.gitbook.io/docs/essential-documentation/roadmap)
- [AppFlowy Public Roadmap](https://github.com/orgs/AppFlowy-IO/projects/5/views/12)
If you'd like to propose a feature, submit an issue [here](https://github.com/AppFlowy-IO/appflowy/issues).
If you'd like to propose a feature, submit a feature request [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=feature_request.yaml&title=%5BFR%5D+) <br/>
If you'd like to report a bug, submit bug report [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=bug_report.yaml&title=%5BBug%5D+)
## **Releases**
@@ -91,7 +95,7 @@ To be honest, we do not claim to outperform Notion in terms of functionality and
## License
Distributed under the AGPLv3 License. See `LICENSE.md` for more information.
Distributed under the AGPLv3 License. See [`LICENSE.md`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/LICENSE) for more information.
## Acknowledgements
+1 -1
View File
@@ -48,7 +48,7 @@
{
"label": "AF: build_flowy_sdk_for_android",
"type": "shell",
"command": "cargo make --profile development-android flowy-sdk-dev-android",
"command": "cargo make --profile development-android appflowy-sdk-dev-android",
"group": "build",
"options": {
"cwd": "${workspaceFolder}"
+2 -1
View File
@@ -22,7 +22,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
CURRENT_APP_VERSION = "0.0.6"
CURRENT_APP_VERSION = "0.0.7"
FEATURES = "flutter"
PRODUCT_NAME = "AppFlowy"
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
@@ -47,6 +47,7 @@ PROTOBUF_DERIVE_CACHE = "../shared-lib/flowy-derive/src/derive_cache/derive_cach
# Test default config
TEST_CRATE_TYPE = "cdylib"
TEST_LIB_EXT = "dylib"
TEST_RUST_LOG = "info"
TEST_BUILD_FLAG = "debug"
TEST_COMPILE_TARGET = "x86_64-apple-darwin"
+5 -5
View File
@@ -12,8 +12,8 @@ When compiling for android we need the following pre-requisites:
- [Download](https://developer.android.com/ndk/downloads/) Android NDK version 24.
- When downloading Android NDK you can get the compressed version as a standalone from the site.
Or you can download it through [Android Studio](https://developer.android.com/studio).
- After downloading the two you need to set the environment variables. For Windows that's a seperate process.
On MacOs and Linux the process is similar.
- After downloading the two you need to set the environment variables. For Windows that's a separate process.
On macOS and Linux the process is similar.
- The variables needed are '$ANDROID_NDK_HOME', this will point to where the NDK is located.
---
@@ -48,9 +48,9 @@ linker = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux
**Folder path: 'Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/14.0.1/lib/linux'.**
After that you have to copy this file into three different folders namely aarch64, arm, i386 and x86_64.
We have to do this so we Android NDK can find clang on our system, if we used NDK 22 we wouldnt have to do this process.
Though using NDK v22 will not give us alot of features to work with.
This github [issue](https://github.com/fzyzcjy/flutter_rust_bridge/issues/419) explains the reason why we are doing this.
We have to do this so we Android NDK can find clang on our system, if we used NDK 22 we wouldn't have to do this process.
Though using NDK v22 will not give us a lot of features to work with.
This GitHub [issue](https://github.com/fzyzcjy/flutter_rust_bridge/issues/419) explains the reason why we are doing this.
---
+43 -3
View File
@@ -161,7 +161,47 @@
"filter": "Filter",
"sortBy": "Sort by",
"Properties": "Properties",
"group": "Group"
"group": "Group",
"addFilter": "Add Filter",
"deleteFilter": "Delete filter",
"filterBy": "Filter by...",
"typeAValue": "Type a value..."
},
"textFilter": {
"contains": "Contains",
"doesNotContain": "Does not contain",
"endsWith": "Ends with",
"startWith": "Starts with",
"is": "Is",
"isNot": "Is not",
"isEmpty": "Is empty",
"isNotEmpty": "Is not empty",
"choicechipPrefix": {
"isNot": "Not",
"startWith": "Starts with",
"endWith": "Ends with",
"isEmpty": "is empty",
"isNotEmpty": "is not empty"
}
},
"checkboxFilter": {
"isChecked": "Checked",
"isUnchecked": "Unchecked",
"choicechipPrefix": {
"is": "is"
}
},
"singleSelectOptionFilter": {
"is": "Is",
"isNot": "Is not",
"isEmpty": "Is empty",
"isNotEmpty": "Is not empty"
},
"multiSelectOptionFilter": {
"contains": "Contains",
"doesNotContain": "Does not contain",
"isEmpty": "Is empty",
"isNotEmpty": "Is not empty"
},
"field": {
"hide": "Hide",
@@ -221,7 +261,7 @@
"menuName": "Grid"
},
"document": {
"menuName": "Doc",
"menuName": "Document",
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
@@ -232,4 +272,4 @@
"create_new_card": "New"
}
}
}
}
@@ -221,7 +221,7 @@
"menuName": "Grille"
},
"document": {
"menuName": "Doc",
"menuName": "Document",
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
@@ -232,4 +232,4 @@
"create_new_card": "Nouveau"
}
}
}
}
@@ -209,7 +209,7 @@
"menuName": "Grid"
},
"document": {
"menuName": "Doc",
"menuName": "Dokter",
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
@@ -220,10 +220,10 @@
"menuName": "Grade"
},
"document": {
"menuName": "Doc",
"menuName": "Documento",
"date": {
"timeHintTextInTwelveHour": "12:00 AM",
"timeHintTextInTwentyFourHour": "12:00"
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
}
},
"board": {
@@ -9,31 +9,38 @@ import 'package:flowy_sdk/rust_stream.dart';
import 'notification_helper.dart';
// GridPB
typedef GridNotificationCallback = void Function(GridNotification, Either<Uint8List, FlowyError>);
typedef GridNotificationCallback = void Function(
GridDartNotification, Either<Uint8List, FlowyError>);
class GridNotificationParser extends NotificationParser<GridNotification, FlowyError> {
GridNotificationParser({String? id, required GridNotificationCallback callback})
class GridNotificationParser
extends NotificationParser<GridDartNotification, FlowyError> {
GridNotificationParser(
{String? id, required GridNotificationCallback callback})
: super(
id: id,
callback: callback,
tyParser: (ty) => GridNotification.valueOf(ty),
tyParser: (ty) => GridDartNotification.valueOf(ty),
errorParser: (bytes) => FlowyError.fromBuffer(bytes),
);
}
typedef GridNotificationHandler = Function(GridNotification ty, Either<Uint8List, FlowyError> result);
typedef GridNotificationHandler = Function(
GridDartNotification ty, Either<Uint8List, FlowyError> result);
class GridNotificationListener {
StreamSubscription<SubscribeObject>? _subscription;
GridNotificationParser? _parser;
GridNotificationListener({required String objectId, required GridNotificationHandler handler})
GridNotificationListener(
{required String objectId, required GridNotificationHandler handler})
: _parser = GridNotificationParser(id: objectId, callback: handler) {
_subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
_subscription =
RustStreamReceiver.listen((observable) => _parser?.parse(observable));
}
Future<void> stop() async {
_parser = null;
await _subscription?.cancel();
_subscription = null;
}
}
@@ -11,7 +11,8 @@ class NetworkListener {
late StreamSubscription<ConnectivityResult> _connectivitySubscription;
NetworkListener() {
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
_connectivitySubscription =
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
}
Future<void> start() async {
@@ -39,9 +40,11 @@ class NetworkListener {
return NetworkType.Ethernet;
case ConnectivityResult.mobile:
return NetworkType.Cell;
case ConnectivityResult.none:
return NetworkType.UnknownNetworkType;
case ConnectivityResult.bluetooth:
return NetworkType.Bluetooth;
case ConnectivityResult.vpn:
return NetworkType.VPN;
case ConnectivityResult.none:
return NetworkType.UnknownNetworkType;
}
}();
@@ -15,6 +15,9 @@ class BlankPluginBuilder extends PluginBuilder {
@override
String get menuName => "Blank";
@override
String get menuIcon => "";
@override
PluginType get pluginType => PluginType.blank;
}
@@ -37,8 +40,7 @@ class BlankPagePlugin extends Plugin {
class BlankPagePluginDisplay extends PluginDisplay with NavigationItem {
@override
Widget get leftBarItem =>
FlowyText.medium(LocaleKeys.blankPageTitle.tr(), fontSize: 12);
Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr());
@override
Widget buildWidget(PluginContext context) => const BlankPage();
@@ -24,7 +24,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
final BoardDataController _gridDataController;
late final AppFlowyBoardController boardController;
final MoveRowFFIService _rowService;
LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap();
final LinkedHashMap<String, GroupController> groupControllers =
LinkedHashMap();
GridFieldController get fieldController =>
_gridDataController.fieldController;
@@ -69,7 +70,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
await event.when(
initial: () async {
_startListening();
await _loadGrid(emit);
await _openGrid(emit);
},
createBottomRow: (groupId) async {
final startRowId = groupControllers[groupId]?.lastRow()?.id;
@@ -135,9 +136,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
}
void _groupItemStartEditing(GroupPB group, RowPB row, bool isEdit) {
final fieldContext = fieldController.getField(group.fieldId);
if (fieldContext == null) {
Log.warn("FieldContext should not be null");
final fieldInfo = fieldController.getField(group.fieldId);
if (fieldInfo == null) {
Log.warn("fieldInfo should not be null");
return;
}
@@ -146,7 +147,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
// group.groupId,
// GroupItem(
// row: row,
// fieldContext: fieldContext,
// fieldInfo: fieldInfo,
// isDraggable: !isEdit,
// ),
// );
@@ -203,7 +204,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
items: _buildGroupItems(group),
customData: GroupData(
group: group,
fieldContext: fieldController.getField(group.fieldId)!,
fieldInfo: fieldController.getField(group.fieldId)!,
),
);
}).toList();
@@ -274,18 +275,18 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
List<AppFlowyGroupItem> _buildGroupItems(GroupPB group) {
final items = group.rows.map((row) {
final fieldContext = fieldController.getField(group.fieldId);
final fieldInfo = fieldController.getField(group.fieldId);
return GroupItem(
row: row,
fieldContext: fieldContext!,
fieldInfo: fieldInfo!,
);
}).toList();
return <AppFlowyGroupItem>[...items];
}
Future<void> _loadGrid(Emitter<BoardState> emit) async {
final result = await _gridDataController.loadData();
Future<void> _openGrid(Emitter<BoardState> emit) async {
final result = await _gridDataController.openGrid();
result.fold(
(grid) => emit(
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
@@ -373,11 +374,11 @@ class GridFieldEquatable extends Equatable {
class GroupItem extends AppFlowyGroupItem {
final RowPB row;
final GridFieldContext fieldContext;
final FieldInfo fieldInfo;
GroupItem({
required this.row,
required this.fieldContext,
required this.fieldInfo,
bool draggable = true,
}) {
super.draggable = draggable;
@@ -400,22 +401,22 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
@override
void insertRow(GroupPB group, RowPB row, int? index) {
final fieldContext = fieldController.getField(group.fieldId);
if (fieldContext == null) {
Log.warn("FieldContext should not be null");
final fieldInfo = fieldController.getField(group.fieldId);
if (fieldInfo == null) {
Log.warn("fieldInfo should not be null");
return;
}
if (index != null) {
final item = GroupItem(
row: row,
fieldContext: fieldContext,
fieldInfo: fieldInfo,
);
controller.insertGroupItem(group.groupId, index, item);
} else {
final item = GroupItem(
row: row,
fieldContext: fieldContext,
fieldInfo: fieldInfo,
);
controller.addGroupItem(group.groupId, item);
}
@@ -428,30 +429,30 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
@override
void updateRow(GroupPB group, RowPB row) {
final fieldContext = fieldController.getField(group.fieldId);
if (fieldContext == null) {
Log.warn("FieldContext should not be null");
final fieldInfo = fieldController.getField(group.fieldId);
if (fieldInfo == null) {
Log.warn("fieldInfo should not be null");
return;
}
controller.updateGroupItem(
group.groupId,
GroupItem(
row: row,
fieldContext: fieldContext,
fieldInfo: fieldInfo,
),
);
}
@override
void addNewRow(GroupPB group, RowPB row, int? index) {
final fieldContext = fieldController.getField(group.fieldId);
if (fieldContext == null) {
Log.warn("FieldContext should not be null");
final fieldInfo = fieldController.getField(group.fieldId);
if (fieldInfo == null) {
Log.warn("fieldInfo should not be null");
return;
}
final item = GroupItem(
row: row,
fieldContext: fieldContext,
fieldInfo: fieldInfo,
draggable: false,
);
@@ -478,10 +479,10 @@ class BoardEditingRow {
class GroupData {
final GroupPB group;
final GridFieldContext fieldContext;
final FieldInfo fieldInfo;
GroupData({
required this.group,
required this.fieldContext,
required this.fieldInfo,
});
CheckboxGroup? asCheckboxGroup() {
@@ -489,7 +490,7 @@ class GroupData {
return CheckboxGroup(group);
}
FieldType get fieldType => fieldContext.fieldType;
FieldType get fieldType => fieldInfo.fieldType;
}
class CheckboxGroup {
@@ -12,7 +12,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
import 'board_listener.dart';
typedef OnFieldsChanged = void Function(UnmodifiableListView<GridFieldContext>);
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldInfo>);
typedef OnGridChanged = void Function(GridPB);
typedef DidLoadGroups = void Function(List<GroupPB>);
typedef OnUpdatedGroup = void Function(List<GroupPB>);
@@ -34,7 +34,8 @@ class BoardDataController {
// key: the block id
final LinkedHashMap<String, GridBlockCache> _blocks;
LinkedHashMap<String, GridBlockCache> get blocks => _blocks;
UnmodifiableMapView<String, GridBlockCache> get blocks =>
UnmodifiableMapView(_blocks);
OnFieldsChanged? _onFieldsChanged;
OnGridChanged? _onGridChanged;
@@ -107,21 +108,22 @@ class BoardDataController {
);
}
Future<Either<Unit, FlowyError>> loadData() async {
final result = await _gridFFIService.loadGrid();
Future<Either<Unit, FlowyError>> openGrid() async {
final result = await _gridFFIService.openGrid();
return Future(
() => result.fold(
(grid) async {
_onGridChanged?.call(grid);
return await fieldController.loadFields(fieldIds: grid.fields).then(
(result) => result.fold(
(l) {
_loadGroups(grid.blocks);
return left(l);
},
(err) => right(err),
),
);
final result = await fieldController.loadFields(
fieldIds: grid.fields,
);
return result.fold(
(l) {
_loadGroups(grid.blocks);
return left(l);
},
(err) => right(err),
);
},
(err) => right(err),
),
@@ -32,18 +32,18 @@ class BoardListener {
}
void _handler(
GridNotification ty,
GridDartNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case GridNotification.DidUpdateGroupView:
case GridDartNotification.DidUpdateGroupView:
result.fold(
(payload) => _groupUpdateNotifier?.value =
left(GroupViewChangesetPB.fromBuffer(payload)),
(error) => _groupUpdateNotifier?.value = right(error),
);
break;
case GridNotification.DidGroupByNewField:
case GridDartNotification.DidGroupByNewField:
result.fold(
(payload) => _groupByNewFieldNotifier?.value =
left(GroupViewChangesetPB.fromBuffer(payload).newGroups),
@@ -35,7 +35,7 @@ class BoardCheckboxCellBloc
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -31,7 +31,7 @@ class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -58,14 +58,14 @@ class BoardDateCellState with _$BoardDateCellState {
const factory BoardDateCellState({
required DateCellDataPB? data,
required String dateStr,
required GridFieldContext fieldContext,
required FieldInfo fieldInfo,
}) = _BoardDateCellState;
factory BoardDateCellState.initial(GridDateCellController context) {
final cellData = context.getCellData();
return BoardDateCellState(
fieldContext: context.fieldContext,
fieldInfo: context.fieldInfo,
data: cellData,
dateStr: _dateStrFromCellData(cellData),
);
@@ -32,7 +32,7 @@ class BoardNumberCellBloc
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -34,7 +34,7 @@ class BoardSelectOptionCellBloc
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -41,7 +41,7 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -38,7 +38,7 @@ class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -63,7 +63,7 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
return RowInfo(
gridId: _rowService.gridId,
fields: UnmodifiableListView(
state.cells.map((cell) => cell.identifier.fieldContext).toList(),
state.cells.map((cell) => cell.identifier.fieldInfo).toList(),
),
rowPB: state.rowPB,
);
@@ -132,10 +132,10 @@ class BoardCellEquatable extends Equatable {
@override
List<Object?> get props {
return [
identifier.fieldContext.id,
identifier.fieldContext.fieldType,
identifier.fieldContext.visibility,
identifier.fieldContext.width,
identifier.fieldInfo.id,
identifier.fieldInfo.fieldType,
identifier.fieldInfo.visibility,
identifier.fieldInfo.width,
];
}
}
@@ -39,7 +39,7 @@ class GroupController {
void startListening() {
_listener.start(onGroupChanged: (result) {
result.fold(
(GroupChangesetPB changeset) {
(GroupRowsNotificationPB changeset) {
for (final deletedRow in changeset.deletedRows) {
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
delegate.removeRow(group, deletedRow);
@@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
typedef UpdateGroupNotifiedValue = Either<GroupChangesetPB, FlowyError>;
typedef UpdateGroupNotifiedValue = Either<GroupRowsNotificationPB, FlowyError>;
class GroupListener {
final GroupPB group;
@@ -27,14 +27,14 @@ class GroupListener {
}
void _handler(
GridNotification ty,
GridDartNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case GridNotification.DidUpdateGroup:
case GridDartNotification.DidUpdateGroup:
result.fold(
(payload) => _groupNotifier?.value =
left(GroupChangesetPB.fromBuffer(payload)),
left(GroupRowsNotificationPB.fromBuffer(payload)),
(error) => _groupNotifier?.value = right(error),
);
break;
@@ -20,6 +20,9 @@ class BoardPluginBuilder implements PluginBuilder {
@override
String get menuName => "Board";
@override
String get menuIcon => "editor/board";
@override
PluginType get pluginType => PluginType.board;
@@ -9,11 +9,9 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_detail.dart';
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:appflowy_board/appflowy_board.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
@@ -22,7 +20,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import '../../grid/application/row/row_cache.dart';
import '../application/board_bloc.dart';
import 'card/card.dart';
@@ -102,27 +99,21 @@ class _BoardContentState extends State<BoardContent> {
}
Widget _buildBoard(BuildContext context) {
return ChangeNotifierProvider.value(
value: Provider.of<AppearanceSetting>(context, listen: true),
child: Selector<AppearanceSetting, AppTheme>(
selector: (ctx, notifier) => notifier.theme,
builder: (ctx, theme, child) => Expanded(
child: AppFlowyBoard(
boardScrollController: scrollManager,
scrollController: ScrollController(),
controller: context.read<BoardBloc>().boardController,
headerBuilder: _buildHeader,
footerBuilder: _buildFooter,
cardBuilder: (_, column, columnItem) => _buildCard(
context,
column,
columnItem,
),
groupConstraints: const BoxConstraints.tightFor(width: 300),
config: AppFlowyBoardConfig(
groupBackgroundColor: theme.bg1,
),
),
return Expanded(
child: AppFlowyBoard(
boardScrollController: scrollManager,
scrollController: ScrollController(),
controller: context.read<BoardBloc>().boardController,
headerBuilder: _buildHeader,
footerBuilder: _buildFooter,
cardBuilder: (_, column, columnItem) => _buildCard(
context,
column,
columnItem,
),
groupConstraints: const BoxConstraints.tightFor(width: 300),
config: AppFlowyBoardConfig(
groupBackgroundColor: Theme.of(context).colorScheme.surfaceVariant,
),
),
);
@@ -159,7 +150,6 @@ class _BoardContentState extends State<BoardContent> {
groupData.headerData.groupName,
fontSize: 14,
overflow: TextOverflow.clip,
color: context.read<AppTheme>().textColor,
),
),
icon: _buildHeaderIcon(boardCustomData),
@@ -168,7 +158,7 @@ class _BoardContentState extends State<BoardContent> {
width: 20,
child: svgWidget(
"home/add",
color: context.read<AppTheme>().iconColor,
color: Theme.of(context).colorScheme.onSurface,
),
),
onAddButtonClick: () {
@@ -191,13 +181,12 @@ class _BoardContentState extends State<BoardContent> {
width: 20,
child: svgWidget(
"home/add",
color: context.read<AppTheme>().iconColor,
color: Theme.of(context).colorScheme.onSurface,
),
),
title: FlowyText.medium(
LocaleKeys.board_column_create_new_card.tr(),
fontSize: 14,
color: context.read<AppTheme>().textColor,
),
height: 50,
margin: config.footerPadding,
@@ -247,7 +236,7 @@ class _BoardContentState extends State<BoardContent> {
child: BoardCard(
gridId: gridId,
groupId: groupData.group.groupId,
fieldId: groupItem.fieldContext.id,
fieldId: groupItem.fieldInfo.id,
isEditing: isEditing,
cellBuilder: cellBuilder,
dataController: cardController,
@@ -276,10 +265,12 @@ class _BoardContentState extends State<BoardContent> {
}
BoxDecoration _makeBoxDecoration(BuildContext context) {
final theme = context.read<AppTheme>();
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
final borderSide = BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0,
);
return BoxDecoration(
color: theme.surface,
color: Theme.of(context).colorScheme.surface,
border: Border.fromBorderSide(borderSide),
borderRadius: const BorderRadius.all(Radius.circular(6)),
);
@@ -294,7 +285,7 @@ class _BoardContentState extends State<BoardContent> {
) {
final rowInfo = RowInfo(
gridId: gridId,
fields: UnmodifiableListView(fieldController.fieldContexts),
fields: UnmodifiableListView(fieldController.fieldInfos),
rowPB: rowPB,
);
@@ -329,15 +320,7 @@ class _ToolbarBlocAdaptor extends StatelessWidget {
fieldController: bloc.fieldController,
);
return ChangeNotifierProvider.value(
value: Provider.of<AppearanceSetting>(context, listen: true),
child: Selector<AppearanceSetting, AppTheme>(
selector: (ctx, notifier) => notifier.theme,
builder: (ctx, theme, child) {
return BoardToolbar(toolbarContext: toolbarContext);
},
),
);
return BoardToolbar(toolbarContext: toolbarContext);
},
);
}
@@ -1,6 +1,5 @@
import 'package:app_flowy/plugins/board/application/card/board_date_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -53,7 +52,7 @@ class _BoardDateCellState extends State<BoardDateCell> {
child: FlowyText.regular(
state.dateStr,
fontSize: 13,
color: context.read<AppTheme>().shader3,
color: Theme.of(context).hintColor,
),
),
);
@@ -1,9 +1,11 @@
import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:textstyle_extensions/textstyle_extensions.dart';
import 'board_cell.dart';
import 'define.dart';
@@ -150,11 +152,7 @@ class _BoardTextCellState extends State<BoardTextCell> {
onChanged: (value) => focusChanged(),
onEditingComplete: () => focusNode.unfocus(),
maxLines: null,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: 'Mulish',
),
style: Theme.of(context).textTheme.bodyMedium!.size(FontSizes.s14),
decoration: InputDecoration(
// Magic number 4 makes the textField take up the same space as FlowyText
contentPadding: EdgeInsets.symmetric(
@@ -1,8 +1,9 @@
import 'package:app_flowy/plugins/board/application/card/board_url_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra/size.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:textstyle_extensions/textstyle_extensions.dart';
import 'define.dart';
@@ -34,7 +35,6 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>(
@@ -53,11 +53,12 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
textAlign: TextAlign.left,
text: TextSpan(
text: state.content,
style: TextStyle(
color: theme.main2,
fontSize: 14,
decoration: TextDecoration.underline,
),
style: Theme.of(context)
.textTheme
.bodyMedium!
.size(FontSizes.s14)
.textColor(Theme.of(context).colorScheme.primary)
.underline,
),
),
),
@@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/board/application/card/card_data_controller.da
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -221,8 +220,10 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(3.0),
child:
svgWidget('grid/details', color: context.read<AppTheme>().iconColor),
child: svgWidget(
'grid/details',
color: Theme.of(context).colorScheme.onSurface,
),
);
}
@@ -243,7 +244,7 @@ class _CardEditOption extends StatelessWidget with CardAccessory {
padding: const EdgeInsets.all(3.0),
child: svgWidget(
'editor/edit',
color: context.read<AppTheme>().iconColor,
color: Theme.of(context).colorScheme.onSurface,
),
);
}
@@ -1,7 +1,5 @@
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
enum AccessoryType {
edit,
@@ -28,7 +26,6 @@ class CardAccessoryContainer extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.read<AppTheme>();
final children = accessories.map((accessory) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
@@ -36,17 +33,16 @@ class CardAccessoryContainer extends StatelessWidget {
accessory.onTap(context);
onTapAccessory(accessory.type);
},
child: _wrapHover(theme, accessory),
child: _wrapHover(context, accessory),
);
}).toList();
return _wrapDecoration(context, Row(children: children));
}
FlowyHover _wrapHover(AppTheme theme, CardAccessory accessory) {
FlowyHover _wrapHover(BuildContext context, CardAccessory accessory) {
return FlowyHover(
style: HoverStyle(
hoverColor: theme.hover,
backgroundColor: theme.surface,
backgroundColor: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.zero,
),
builder: (_, onHover) => SizedBox(
@@ -58,8 +54,10 @@ class CardAccessoryContainer extends StatelessWidget {
}
Widget _wrapDecoration(BuildContext context, Widget child) {
final theme = context.read<AppTheme>();
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
final borderSide = BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0,
);
final decoration = BoxDecoration(
color: Colors.transparent,
border: Border.fromBorderSide(borderSide),
@@ -7,7 +7,6 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_propert
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
@@ -95,7 +94,6 @@ class _SettingItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.read<AppTheme>();
final isSelected = context
.read<BoardSettingBloc>()
.state
@@ -106,18 +104,16 @@ class _SettingItem extends StatelessWidget {
height: 30,
child: FlowyButton(
isSelected: isSelected,
text: FlowyText.medium(
action.title(),
fontSize: 12,
color: theme.textColor,
),
hoverColor: theme.hover,
text: FlowyText.medium(action.title()),
onTap: () {
context
.read<BoardSettingBloc>()
.add(BoardSettingEvent.performAction(action));
},
leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
leftIcon: svgWidget(
action.iconName(),
color: Theme.of(context).colorScheme.onSurface,
),
),
);
}
@@ -1,12 +1,12 @@
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/color_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flutter/material.dart';
import '../../../../generated/locale_keys.g.dart';
import 'board_setting.dart';
class BoardToolbarContext {
@@ -32,6 +32,7 @@ class BoardToolbar extends StatelessWidget {
height: 40,
child: Row(
children: [
const Spacer(),
_SettingButton(
settingContext: BoardSettingContext.from(toolbarContext),
),
@@ -61,17 +62,20 @@ class _SettingButtonState extends State<_SettingButton> {
@override
Widget build(BuildContext context) {
final theme = context.read<AppTheme>();
return AppFlowyPopover(
controller: popoverController,
direction: PopoverDirection.leftWithTopAligned,
triggerActions: PopoverTriggerFlags.none,
constraints: BoxConstraints.loose(const Size(260, 400)),
child: FlowyIconButton(
hoverColor: theme.hover,
width: 22,
icon: Padding(
padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
child: svgWidget("grid/setting/setting", color: theme.iconColor),
),
child: FlowyTextButton(
LocaleKeys.settings_title.tr(),
fontSize: 14,
fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 6),
onPressed: () {
popoverController.show();
},
),
popupBuilder: (BuildContext popoverContext) {
return BoardSettingListPopover(
@@ -1,61 +0,0 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
EditorStyle customEditorStyle(BuildContext context) {
final theme = context.watch<AppTheme>();
const baseFontSize = 14.0;
const basePadding = 12.0;
var textStyle = theme.isDark
? BuiltInTextStyle.builtInDarkMode()
: BuiltInTextStyle.builtIn();
textStyle = textStyle.copyWith(
defaultTextStyle: textStyle.defaultTextStyle.copyWith(
fontFamily: 'poppins',
fontSize: baseFontSize,
),
bold: textStyle.bold.copyWith(
fontWeight: FontWeight.w500,
),
);
return EditorStyle.defaultStyle().copyWith(
padding: const EdgeInsets.symmetric(horizontal: 80),
textStyle: textStyle,
pluginStyles: {
'text/heading': builtInPluginStyle
..update(
'textStyle',
(_) => (EditorState editorState, Node node) {
final headingToFontSize = {
'h1': baseFontSize + 12,
'h2': baseFontSize + 8,
'h3': baseFontSize + 4,
'h4': baseFontSize,
'h5': baseFontSize,
'h6': baseFontSize,
};
final fontSize =
headingToFontSize[node.attributes.heading] ?? baseFontSize;
return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600);
},
)
..update(
'padding',
(_) => (EditorState editorState, Node node) {
final headingToPadding = {
'h1': basePadding + 6,
'h2': basePadding + 4,
'h3': basePadding + 2,
'h4': basePadding,
'h5': basePadding,
'h6': basePadding,
};
final padding =
headingToPadding[node.attributes.heading] ?? basePadding;
return EdgeInsets.only(bottom: padding);
},
)
},
);
}
@@ -1,74 +0,0 @@
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
class StyleWidgetBuilder {
static QuillCheckboxBuilder checkbox(AppTheme theme) {
return EditorCheckboxBuilder(theme);
}
}
class EditorCheckboxBuilder extends QuillCheckboxBuilder {
final AppTheme theme;
EditorCheckboxBuilder(this.theme);
@override
Widget build(
{required BuildContext context,
required bool isChecked,
required ValueChanged<bool> onChanged}) {
return FlowyEditorCheckbox(
theme: theme,
isChecked: isChecked,
onChanged: onChanged,
);
}
}
class FlowyEditorCheckbox extends StatefulWidget {
final bool isChecked;
final ValueChanged<bool> onChanged;
final AppTheme theme;
const FlowyEditorCheckbox({
required this.theme,
required this.isChecked,
required this.onChanged,
Key? key,
}) : super(key: key);
@override
FlowyEditorCheckboxState createState() => FlowyEditorCheckboxState();
}
class FlowyEditorCheckboxState extends State<FlowyEditorCheckbox> {
late bool isChecked;
@override
void initState() {
isChecked = widget.isChecked;
super.initState();
}
@override
Widget build(BuildContext context) {
final icon = isChecked
? svgWidget('editor/editor_check')
: svgWidget('editor/editor_uncheck');
return Align(
alignment: Alignment.centerLeft,
child: FlowyIconButton(
onPressed: () {
isChecked = !isChecked;
widget.onChanged(isChecked);
setState(() {});
},
iconPadding: EdgeInsets.zero,
icon: icon,
width: 23,
),
);
}
}
@@ -1,97 +0,0 @@
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/models/documents/style.dart';
import 'package:flutter/material.dart';
import 'toolbar_icon_button.dart';
class FlowyCheckListButton extends StatefulWidget {
const FlowyCheckListButton({
required this.controller,
required this.attribute,
required this.tooltipText,
this.iconSize = defaultIconSize,
this.fillColor,
this.childBuilder = defaultToggleStyleButtonBuilder,
Key? key,
}) : super(key: key);
final double iconSize;
final Color? fillColor;
final QuillController controller;
final ToggleStyleButtonBuilder childBuilder;
final Attribute attribute;
final String tooltipText;
@override
FlowyCheckListButtonState createState() => FlowyCheckListButtonState();
}
class FlowyCheckListButtonState extends State<FlowyCheckListButton> {
bool? _isToggled;
Style get _selectionStyle => widget.controller.getSelectionStyle();
void _didChangeEditingValue() {
setState(() {
_isToggled =
_getIsToggled(widget.controller.getSelectionStyle().attributes);
});
}
@override
void initState() {
super.initState();
_isToggled = _getIsToggled(_selectionStyle.attributes);
widget.controller.addListener(_didChangeEditingValue);
}
bool _getIsToggled(Map<String, Attribute> attrs) {
if (widget.attribute.key == Attribute.list.key) {
final attribute = attrs[widget.attribute.key];
if (attribute == null) {
return false;
}
return attribute.value == widget.attribute.value ||
attribute.value == Attribute.checked.value;
}
return attrs.containsKey(widget.attribute.key);
}
@override
void didUpdateWidget(covariant FlowyCheckListButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_didChangeEditingValue);
widget.controller.addListener(_didChangeEditingValue);
_isToggled = _getIsToggled(_selectionStyle.attributes);
}
}
@override
void dispose() {
widget.controller.removeListener(_didChangeEditingValue);
super.dispose();
}
@override
Widget build(BuildContext context) {
return ToolbarIconButton(
onPressed: _toggleAttribute,
width: widget.iconSize * kIconButtonFactor,
iconName: 'editor/checkbox',
isToggled: _isToggled ?? false,
tooltipText: widget.tooltipText,
);
}
void _toggleAttribute() {
widget.controller.formatSelection(_isToggled!
? Attribute.clone(Attribute.unchecked, null)
: Attribute.unchecked);
}
}
@@ -1,280 +0,0 @@
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/models/documents/style.dart';
import 'package:flutter_quill/utils/color.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'toolbar_icon_button.dart';
class FlowyColorButton extends StatefulWidget {
const FlowyColorButton({
required this.icon,
required this.controller,
required this.background,
this.iconSize = defaultIconSize,
this.iconTheme,
Key? key,
}) : super(key: key);
final IconData icon;
final double iconSize;
final bool background;
final QuillController controller;
final QuillIconTheme? iconTheme;
@override
FlowyColorButtonState createState() => FlowyColorButtonState();
}
class FlowyColorButtonState extends State<FlowyColorButton> {
late bool _isToggledColor;
late bool _isToggledBackground;
late bool _isWhite;
late bool _isWhitebackground;
Style get _selectionStyle => widget.controller.getSelectionStyle();
void _didChangeEditingValue() {
setState(() {
_isToggledColor =
_getIsToggledColor(widget.controller.getSelectionStyle().attributes);
_isToggledBackground = _getIsToggledBackground(
widget.controller.getSelectionStyle().attributes);
_isWhite = _isToggledColor &&
_selectionStyle.attributes['color']!.value == '#ffffff';
_isWhitebackground = _isToggledBackground &&
_selectionStyle.attributes['background']!.value == '#ffffff';
});
}
@override
void initState() {
super.initState();
_isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
_isToggledBackground = _getIsToggledBackground(_selectionStyle.attributes);
_isWhite = _isToggledColor &&
_selectionStyle.attributes['color']!.value == '#ffffff';
_isWhitebackground = _isToggledBackground &&
_selectionStyle.attributes['background']!.value == '#ffffff';
widget.controller.addListener(_didChangeEditingValue);
}
bool _getIsToggledColor(Map<String, Attribute> attrs) {
return attrs.containsKey(Attribute.color.key);
}
bool _getIsToggledBackground(Map<String, Attribute> attrs) {
return attrs.containsKey(Attribute.background.key);
}
@override
void didUpdateWidget(covariant FlowyColorButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_didChangeEditingValue);
widget.controller.addListener(_didChangeEditingValue);
_isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
_isToggledBackground =
_getIsToggledBackground(_selectionStyle.attributes);
_isWhite = _isToggledColor &&
_selectionStyle.attributes['color']!.value == '#ffffff';
_isWhitebackground = _isToggledBackground &&
_selectionStyle.attributes['background']!.value == '#ffffff';
}
}
@override
void dispose() {
widget.controller.removeListener(_didChangeEditingValue);
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final fillColor = _isToggledColor && !widget.background && _isWhite
? stringToColor('#ffffff')
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
final fillColorBackground =
_isToggledBackground && widget.background && _isWhitebackground
? stringToColor('#ffffff')
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
return Tooltip(
message: LocaleKeys.toolbar_highlight.tr(),
showDuration: Duration.zero,
child: QuillIconButton(
highlightElevation: 0,
hoverElevation: 0,
size: widget.iconSize * kIconButtonFactor,
icon: Icon(widget.icon,
size: widget.iconSize, color: theme.iconTheme.color),
fillColor: widget.background ? fillColorBackground : fillColor,
onPressed: _showColorPicker,
),
);
}
void _changeColor(BuildContext context, Color color) {
var hex = color.value.toRadixString(16);
if (hex.startsWith('ff')) {
hex = hex.substring(2);
}
hex = '#$hex';
widget.controller.formatSelection(
widget.background ? BackgroundAttribute(hex) : ColorAttribute(hex));
Navigator.of(context).pop();
}
void _showColorPicker() {
final style = widget.controller.getSelectionStyle();
final values = style.values
.where((v) => v.key == Attribute.background.key)
.map((v) => v.value);
int initialColor = 0;
if (values.isNotEmpty) {
assert(values.length == 1);
initialColor = stringToHex(values.first);
}
StyledDialog(
child: SingleChildScrollView(
child: FlowyColorPicker(
onColorChanged: (color) {
if (color == null) {
widget.controller.formatSelection(BackgroundAttribute(null));
Navigator.of(context).pop();
} else {
_changeColor(context, color);
}
},
initialColor: initialColor,
),
),
).show(context);
}
}
int stringToHex(String code) {
return int.parse(code.substring(1, 7), radix: 16) + 0xFF000000;
}
class FlowyColorPicker extends StatefulWidget {
final List<int> colors = [
0xffe8e0ff,
0xffffe7fd,
0xffffe7ee,
0xffffefe3,
0xfffff2cd,
0xfff5ffdc,
0xffddffd6,
0xffdefff1,
];
final Function(Color?) onColorChanged;
final int initialColor;
FlowyColorPicker(
{Key? key, required this.onColorChanged, this.initialColor = 0})
: super(key: key);
@override
State<FlowyColorPicker> createState() => _FlowyColorPickerState();
}
// if (shrinkWrap) {
// innerContent = IntrinsicWidth(child: IntrinsicHeight(child: innerContent));
// }
class _FlowyColorPickerState extends State<FlowyColorPicker> {
@override
Widget build(BuildContext context) {
const double width = 480;
const int crossAxisCount = 6;
const double mainAxisSpacing = 10;
const double crossAxisSpacing = 10;
final numberOfRows = (widget.colors.length / crossAxisCount).ceil();
const perRowHeight =
((width - ((crossAxisCount - 1) * mainAxisSpacing)) / crossAxisCount);
final totalHeight =
numberOfRows * perRowHeight + numberOfRows * crossAxisSpacing;
return Container(
constraints: BoxConstraints.tightFor(width: width, height: totalHeight),
child: CustomScrollView(
scrollDirection: Axis.vertical,
controller: ScrollController(),
physics: const ClampingScrollPhysics(),
slivers: [
SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (widget.colors.length > index) {
final isSelected =
widget.colors[index] == widget.initialColor;
return ColorItem(
color: Color(widget.colors[index]),
onPressed: widget.onColorChanged,
isSelected: isSelected,
);
} else {
return null;
}
},
childCount: widget.colors.length,
),
),
],
),
);
}
}
class ColorItem extends StatelessWidget {
final Function(Color?) onPressed;
final bool isSelected;
final Color color;
const ColorItem({
Key? key,
required this.color,
required this.onPressed,
this.isSelected = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
if (!isSelected) {
return RawMaterialButton(
onPressed: () {
onPressed(color);
},
elevation: 0,
hoverElevation: 0.6,
fillColor: color,
shape: const CircleBorder(),
);
} else {
return RawMaterialButton(
shape: const CircleBorder(
side: BorderSide(color: Colors.white, width: 8)) +
CircleBorder(side: BorderSide(color: color, width: 4)),
onPressed: () {
if (isSelected) {
onPressed(null);
} else {
onPressed(color);
}
},
elevation: 1.0,
hoverElevation: 0.6,
fillColor: color,
);
}
}
}
@@ -1,101 +0,0 @@
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/models/documents/style.dart';
import 'package:flutter/material.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'toolbar_icon_button.dart';
class FlowyHeaderStyleButton extends StatefulWidget {
const FlowyHeaderStyleButton({
required this.controller,
this.iconSize = defaultIconSize,
Key? key,
}) : super(key: key);
final QuillController controller;
final double iconSize;
@override
FlowyHeaderStyleButtonState createState() => FlowyHeaderStyleButtonState();
}
class FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
Attribute? _value;
Style get _selectionStyle => widget.controller.getSelectionStyle();
@override
void initState() {
super.initState();
setState(() {
_value =
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
});
widget.controller.addListener(_didChangeEditingValue);
}
@override
Widget build(BuildContext context) {
final valueToText = <Attribute, String>{
Attribute.h1: 'H1',
Attribute.h2: 'H2',
Attribute.h3: 'H3',
};
final valueAttribute = <Attribute>[
Attribute.h1,
Attribute.h2,
Attribute.h3
];
final valueString = <String>['H1', 'H2', 'H3'];
final attributeImageName = <String>['editor/H1', 'editor/H2', 'editor/H3'];
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(3, (index) {
// final child =
// _valueToText[_value] == _valueString[index] ? svg('editor/H1', color: Colors.white) : svg('editor/H1');
final headerTitle = "${LocaleKeys.toolbar_header.tr()} ${index + 1}";
final isToggled = valueToText[_value] == valueString[index];
return ToolbarIconButton(
onPressed: () {
if (isToggled) {
widget.controller.formatSelection(Attribute.header);
} else {
widget.controller.formatSelection(valueAttribute[index]);
}
},
width: widget.iconSize * kIconButtonFactor,
iconName: attributeImageName[index],
isToggled: isToggled,
tooltipText: headerTitle,
);
}),
);
}
void _didChangeEditingValue() {
setState(() {
_value =
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
});
}
@override
void didUpdateWidget(covariant FlowyHeaderStyleButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_didChangeEditingValue);
widget.controller.addListener(_didChangeEditingValue);
_value =
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
}
}
@override
void dispose() {
widget.controller.removeListener(_didChangeEditingValue);
super.dispose();
}
}
@@ -1,33 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
class FlowyHistoryButton extends StatelessWidget {
final IconData icon;
final double iconSize;
final bool undo;
final QuillController controller;
final String tooltipText;
const FlowyHistoryButton({
required this.icon,
required this.controller,
required this.undo,
required this.tooltipText,
required this.iconSize,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Tooltip(
message: tooltipText,
showDuration: Duration.zero,
child: HistoryButton(
icon: icon,
iconSize: iconSize,
controller: controller,
undo: undo,
),
);
}
}
@@ -1,85 +0,0 @@
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter/material.dart';
import 'toolbar_icon_button.dart';
class FlowyImageButton extends StatelessWidget {
const FlowyImageButton({
required this.controller,
required this.tooltipText,
this.iconSize = defaultIconSize,
this.onImagePickCallback,
this.fillColor,
this.filePickImpl,
this.webImagePickImpl,
this.mediaPickSettingSelector,
Key? key,
}) : super(key: key);
final double iconSize;
final Color? fillColor;
final QuillController controller;
final OnImagePickCallback? onImagePickCallback;
final WebImagePickImpl? webImagePickImpl;
final FilePickImpl? filePickImpl;
final MediaPickSettingSelector? mediaPickSettingSelector;
final String tooltipText;
@override
Widget build(BuildContext context) {
return ToolbarIconButton(
iconName: 'editor/image',
width: iconSize * 1.77,
onPressed: () => _onPressedHandler(context),
isToggled: false,
tooltipText: tooltipText,
);
}
Future<void> _onPressedHandler(BuildContext context) async {
// if (onImagePickCallback != null) {
// final selector = mediaPickSettingSelector ?? ImageVideoUtils.selectMediaPickSetting;
// final source = await selector(context);
// if (source != null) {
// if (source == MediaPickSetting.Gallery) {
// _pickImage(context);
// } else {
// _typeLink(context);
// }
// }
// } else {
// _typeLink(context);
// }
}
// void _pickImage(BuildContext context) => ImageVideoUtils.handleImageButtonTap(
// context,
// controller,
// ImageSource.gallery,
// onImagePickCallback!,
// filePickImpl: filePickImpl,
// webImagePickImpl: webImagePickImpl,
// );
// void _typeLink(BuildContext context) {
// TextFieldDialog(
// title: 'URL',
// value: "",
// confirm: (newValue) {
// if (newValue.isEmpty) {
// return;
// }
// final index = controller.selection.baseOffset;
// final length = controller.selection.extentOffset - index;
// controller.replaceText(index, length, BlockEmbed.image(newValue), null);
// },
// ).show(context);
// }
}
@@ -1,98 +0,0 @@
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'toolbar_icon_button.dart';
class FlowyLinkStyleButton extends StatefulWidget {
const FlowyLinkStyleButton({
required this.controller,
this.iconSize = defaultIconSize,
Key? key,
}) : super(key: key);
final QuillController controller;
final double iconSize;
@override
FlowyLinkStyleButtonState createState() => FlowyLinkStyleButtonState();
}
class FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
void _didChangeSelection() {
setState(() {});
}
@override
void initState() {
super.initState();
widget.controller.addListener(_didChangeSelection);
}
@override
void didUpdateWidget(covariant FlowyLinkStyleButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_didChangeSelection);
widget.controller.addListener(_didChangeSelection);
}
}
@override
void dispose() {
super.dispose();
widget.controller.removeListener(_didChangeSelection);
}
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final isEnabled = !widget.controller.selection.isCollapsed;
final pressedHandler = isEnabled ? () => _openLinkDialog(context) : null;
final icon = isEnabled
? svgWidget(
'editor/share',
color: theme.iconColor,
)
: svgWidget(
'editor/share',
color: theme.disableIconColor,
);
return FlowyIconButton(
onPressed: pressedHandler,
iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
icon: icon,
fillColor: theme.shader6,
hoverColor: theme.shader5,
width: widget.iconSize * kIconButtonFactor,
);
}
void _openLinkDialog(BuildContext context) {
final style = widget.controller.getSelectionStyle();
final values = style.values
.where((v) => v.key == Attribute.link.key)
.map((v) => v.value);
String value = "";
if (values.isNotEmpty) {
assert(values.length == 1);
value = values.first;
}
NavigatorTextFieldDialog(
title: 'URL',
value: value,
confirm: (newValue) {
if (newValue.isEmpty) {
return;
}
widget.controller.formatSelection(LinkAttribute(newValue));
},
).show(context);
}
}
@@ -1,84 +0,0 @@
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/models/documents/style.dart';
import 'package:flutter/material.dart';
import 'toolbar_icon_button.dart';
class FlowyToggleStyleButton extends StatefulWidget {
final Attribute attribute;
final String normalIcon;
final double iconSize;
final QuillController controller;
final String tooltipText;
const FlowyToggleStyleButton({
required this.attribute,
required this.normalIcon,
required this.controller,
required this.tooltipText,
this.iconSize = defaultIconSize,
Key? key,
}) : super(key: key);
@override
ToggleStyleButtonState createState() => ToggleStyleButtonState();
}
class ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
bool? _isToggled;
Style get _selectionStyle => widget.controller.getSelectionStyle();
@override
void initState() {
super.initState();
_isToggled = _getIsToggled(_selectionStyle.attributes);
widget.controller.addListener(_didChangeEditingValue);
}
@override
Widget build(BuildContext context) {
return ToolbarIconButton(
onPressed: _toggleAttribute,
width: widget.iconSize * kIconButtonFactor,
isToggled: _isToggled ?? false,
iconName: widget.normalIcon,
tooltipText: widget.tooltipText,
);
}
@override
void didUpdateWidget(covariant FlowyToggleStyleButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_didChangeEditingValue);
widget.controller.addListener(_didChangeEditingValue);
_isToggled = _getIsToggled(_selectionStyle.attributes);
}
}
@override
void dispose() {
widget.controller.removeListener(_didChangeEditingValue);
super.dispose();
}
void _didChangeEditingValue() {
setState(() => _isToggled = _getIsToggled(_selectionStyle.attributes));
}
bool _getIsToggled(Map<String, Attribute> attrs) {
if (widget.attribute.key == Attribute.list.key) {
final attribute = attrs[widget.attribute.key];
if (attribute == null) {
return false;
}
return attribute.value == widget.attribute.value;
}
return attrs.containsKey(widget.attribute.key);
}
void _toggleAttribute() {
widget.controller.formatSelection(_isToggled!
? Attribute.clone(widget.attribute, null)
: widget.attribute);
}
}
@@ -1,308 +0,0 @@
import 'dart:async';
import 'dart:math';
import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter/material.dart';
import 'package:styled_widget/styled_widget.dart';
import 'check_button.dart';
import 'color_picker.dart';
import 'header_button.dart';
import 'history_button.dart';
import 'link_button.dart';
import 'toggle_button.dart';
import 'toolbar_icon_button.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
final List<Widget> children;
final double toolBarHeight;
final Color? color;
const EditorToolbar({
required this.children,
this.toolBarHeight = 46,
this.color,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).canvasColor,
constraints: BoxConstraints.tightFor(height: preferredSize.height),
child: ToolbarButtonList(buttons: children)
.padding(horizontal: 4, vertical: 4),
);
}
@override
Size get preferredSize => Size.fromHeight(toolBarHeight);
factory EditorToolbar.basic({
required QuillController controller,
double toolbarIconSize = defaultIconSize,
OnImagePickCallback? onImagePickCallback,
OnVideoPickCallback? onVideoPickCallback,
MediaPickSettingSelector? mediaPickSettingSelector,
FilePickImpl? filePickImpl,
WebImagePickImpl? webImagePickImpl,
WebVideoPickImpl? webVideoPickImpl,
Key? key,
}) {
return EditorToolbar(
key: key,
toolBarHeight: toolbarIconSize * 2,
children: [
FlowyHistoryButton(
icon: Icons.undo_outlined,
iconSize: toolbarIconSize,
controller: controller,
undo: true,
tooltipText: LocaleKeys.toolbar_undo.tr(),
),
FlowyHistoryButton(
icon: Icons.redo_outlined,
iconSize: toolbarIconSize,
controller: controller,
undo: false,
tooltipText: LocaleKeys.toolbar_redo.tr(),
),
FlowyToggleStyleButton(
attribute: Attribute.bold,
normalIcon: 'editor/bold',
iconSize: toolbarIconSize,
controller: controller,
tooltipText: LocaleKeys.toolbar_bold.tr(),
),
FlowyToggleStyleButton(
attribute: Attribute.italic,
normalIcon: 'editor/italic',
iconSize: toolbarIconSize,
controller: controller,
tooltipText: LocaleKeys.toolbar_italic.tr(),
),
FlowyToggleStyleButton(
attribute: Attribute.underline,
normalIcon: 'editor/underline',
iconSize: toolbarIconSize,
controller: controller,
tooltipText: LocaleKeys.toolbar_underline.tr(),
),
FlowyToggleStyleButton(
attribute: Attribute.strikeThrough,
normalIcon: 'editor/strikethrough',
iconSize: toolbarIconSize,
controller: controller,
tooltipText: LocaleKeys.toolbar_strike.tr(),
),
FlowyColorButton(
icon: Icons.format_color_fill,
iconSize: toolbarIconSize,
controller: controller,
background: true,
),
// FlowyImageButton(
// iconSize: toolbarIconSize,
// controller: controller,
// onImagePickCallback: onImagePickCallback,
// filePickImpl: filePickImpl,
// webImagePickImpl: webImagePickImpl,
// mediaPickSettingSelector: mediaPickSettingSelector,
// ),
FlowyHeaderStyleButton(
controller: controller,
iconSize: toolbarIconSize,
),
FlowyToggleStyleButton(
attribute: Attribute.ol,
controller: controller,
normalIcon: 'editor/numbers',
iconSize: toolbarIconSize,
tooltipText: LocaleKeys.toolbar_numList.tr(),
),
FlowyToggleStyleButton(
attribute: Attribute.ul,
controller: controller,
normalIcon: 'editor/bullet_list',
iconSize: toolbarIconSize,
tooltipText: LocaleKeys.toolbar_bulletList.tr(),
),
FlowyCheckListButton(
attribute: Attribute.unchecked,
controller: controller,
iconSize: toolbarIconSize,
tooltipText: LocaleKeys.toolbar_checkList.tr(),
),
FlowyToggleStyleButton(
attribute: Attribute.inlineCode,
controller: controller,
normalIcon: 'editor/inline_block',
iconSize: toolbarIconSize,
tooltipText: LocaleKeys.toolbar_inlineCode.tr(),
),
FlowyToggleStyleButton(
attribute: Attribute.blockQuote,
controller: controller,
normalIcon: 'editor/quote',
iconSize: toolbarIconSize,
tooltipText: LocaleKeys.toolbar_quote.tr(),
),
FlowyLinkStyleButton(
controller: controller,
iconSize: toolbarIconSize,
),
FlowyEmojiStyleButton(
normalIcon: 'editor/insert_emoticon',
controller: controller,
tooltipText: "Emoji Picker",
),
],
);
}
}
class ToolbarButtonList extends StatefulWidget {
const ToolbarButtonList({required this.buttons, Key? key}) : super(key: key);
final List<Widget> buttons;
@override
ToolbarButtonListState createState() => ToolbarButtonListState();
}
class ToolbarButtonListState extends State<ToolbarButtonList>
with WidgetsBindingObserver {
final ScrollController _controller = ScrollController();
bool _showLeftArrow = false;
bool _showRightArrow = false;
@override
void initState() {
super.initState();
_controller.addListener(_handleScroll);
// Listening to the WidgetsBinding instance is necessary so that we can
// hide the arrows when the window gets a new size and thus the toolbar
// becomes scrollable/unscrollable.
WidgetsBinding.instance.addObserver(this);
// Workaround to allow the scroll controller attach to our ListView so that
// we can detect if overflow arrows need to be shown on init.
Timer.run(_handleScroll);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
List<Widget> children = [];
double width =
(widget.buttons.length + 2) * defaultIconSize * kIconButtonFactor;
final isFit = constraints.maxWidth > width;
if (!isFit) {
children.add(_buildLeftArrow());
width = width + 18;
}
children.add(_buildScrollableList(constraints, isFit));
if (!isFit) {
children.add(_buildRightArrow());
width = width + 18;
}
return SizedBox(
width: min(constraints.maxWidth, width),
child: Row(
children: children,
),
);
},
);
}
@override
void didChangeMetrics() => _handleScroll();
@override
void dispose() {
_controller.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
void _handleScroll() {
if (!mounted) return;
setState(() {
_showLeftArrow =
_controller.position.minScrollExtent != _controller.position.pixels;
_showRightArrow =
_controller.position.maxScrollExtent != _controller.position.pixels;
});
}
Widget _buildLeftArrow() {
return SizedBox(
width: 8,
child: Transform.translate(
// Move the icon a few pixels to center it
offset: const Offset(-5, 0),
child: _showLeftArrow ? const Icon(Icons.arrow_left, size: 18) : null,
),
);
}
// [[sliver: https://medium.com/flutter/slivers-demystified-6ff68ab0296f]]
Widget _buildScrollableList(BoxConstraints constraints, bool isFit) {
Widget child = Expanded(
child: CustomScrollView(
scrollDirection: Axis.horizontal,
controller: _controller,
physics: const ClampingScrollPhysics(),
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return widget.buttons[index];
},
childCount: widget.buttons.length,
addAutomaticKeepAlives: false,
),
)
],
),
);
if (!isFit) {
child = ScrollConfiguration(
// Remove the glowing effect, as we already have the arrow indicators
behavior: _NoGlowBehavior(),
// The CustomScrollView is necessary so that the children are not
// stretched to the height of the toolbar, https://bit.ly/3uC3bjI
child: child,
);
}
return child;
}
Widget _buildRightArrow() {
return SizedBox(
width: 8,
child: Transform.translate(
// Move the icon a few pixels to center it
offset: const Offset(-5, 0),
child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null,
),
);
}
}
class _NoGlowBehavior extends ScrollBehavior {
@override
Widget buildViewportChrome(BuildContext _, Widget child, AxisDirection __) {
return child;
}
}
@@ -1,38 +0,0 @@
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/material.dart';
import 'package:flowy_infra/theme.dart';
import 'package:provider/provider.dart';
const double defaultIconSize = 18;
class ToolbarIconButton extends StatelessWidget {
final double width;
final VoidCallback? onPressed;
final bool isToggled;
final String iconName;
final String tooltipText;
const ToolbarIconButton({
Key? key,
required this.onPressed,
required this.isToggled,
required this.width,
required this.iconName,
required this.tooltipText,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyIconButton(
iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
onPressed: onPressed,
width: width,
icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName, color: theme.iconColor),
fillColor: isToggled == true ? theme.main1 : theme.shader6,
hoverColor: isToggled == true ? theme.main1 : theme.hover,
tooltipText: tooltipText,
);
}
}
@@ -1,133 +0,0 @@
import 'package:app_flowy/plugins/doc/presentation/style_widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
import 'package:flowy_infra/theme.dart';
DefaultStyles customStyles(BuildContext context) {
const baseSpacing = Tuple2<double, double>(6, 0);
final theme = context.watch<AppTheme>();
final themeData = theme.themeData;
final fontFamily = makeFontFamily(themeData);
final defaultTextStyle = DefaultTextStyle.of(context);
final baseStyle = defaultTextStyle.style.copyWith(
fontSize: 18,
height: 1.3,
fontWeight: FontWeight.w300,
letterSpacing: 0.6,
fontFamily: fontFamily,
);
return DefaultStyles(
h1: DefaultTextBlockStyle(
defaultTextStyle.style.copyWith(
fontSize: 34,
color: defaultTextStyle.style.color!.withOpacity(0.70),
height: 1.15,
fontWeight: FontWeight.w300,
),
const Tuple2(16, 0),
const Tuple2(0, 0),
null),
h2: DefaultTextBlockStyle(
defaultTextStyle.style.copyWith(
fontSize: 24,
color: defaultTextStyle.style.color!.withOpacity(0.70),
height: 1.15,
fontWeight: FontWeight.normal,
),
const Tuple2(8, 0),
const Tuple2(0, 0),
null),
h3: DefaultTextBlockStyle(
defaultTextStyle.style.copyWith(
fontSize: 20,
color: defaultTextStyle.style.color!.withOpacity(0.70),
height: 1.25,
fontWeight: FontWeight.w500,
),
const Tuple2(8, 0),
const Tuple2(0, 0),
null),
paragraph: DefaultTextBlockStyle(
baseStyle, const Tuple2(10, 0), const Tuple2(0, 0), null),
bold: const TextStyle(fontWeight: FontWeight.bold),
italic: const TextStyle(fontStyle: FontStyle.italic),
small: const TextStyle(fontSize: 12, color: Colors.black45),
underline: const TextStyle(decoration: TextDecoration.underline),
strikeThrough: const TextStyle(decoration: TextDecoration.lineThrough),
inlineCode: TextStyle(
color: Colors.blue.shade900.withOpacity(0.9),
fontFamily: fontFamily,
fontSize: 13,
),
link: TextStyle(
color: themeData.colorScheme.secondary,
decoration: TextDecoration.underline,
),
color: theme.textColor,
placeHolder: DefaultTextBlockStyle(
defaultTextStyle.style.copyWith(
fontSize: 20,
height: 1.5,
color: Colors.grey.withOpacity(0.6),
),
const Tuple2(0, 0),
const Tuple2(0, 0),
null),
lists: DefaultListBlockStyle(baseStyle, baseSpacing, const Tuple2(0, 6),
null, StyleWidgetBuilder.checkbox(theme)),
quote: DefaultTextBlockStyle(
TextStyle(color: baseStyle.color!.withOpacity(0.6)),
baseSpacing,
const Tuple2(6, 2),
BoxDecoration(
border: Border(
left: BorderSide(width: 4, color: theme.shader5),
),
)),
code: DefaultTextBlockStyle(
TextStyle(
color: Colors.blue.shade900.withOpacity(0.9),
fontFamily: fontFamily,
fontSize: 13,
height: 1.15,
),
baseSpacing,
const Tuple2(0, 0),
BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(2),
)),
indent: DefaultTextBlockStyle(
baseStyle, baseSpacing, const Tuple2(0, 6), null),
align: DefaultTextBlockStyle(
baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null),
leading: DefaultTextBlockStyle(
baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null),
sizeSmall: const TextStyle(fontSize: 10),
sizeLarge: const TextStyle(fontSize: 18),
sizeHuge: const TextStyle(fontSize: 22));
}
String makeFontFamily(ThemeData themeData) {
String fontFamily;
switch (themeData.platform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
fontFamily = 'Mulish';
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.windows:
case TargetPlatform.linux:
fontFamily = 'Roboto Mono';
break;
default:
throw UnimplementedError();
}
return fontFamily;
}
@@ -1,7 +1,7 @@
import 'dart:convert';
import 'package:app_flowy/plugins/trash/application/trash_service.dart';
import 'package:app_flowy/workspace/application/view/view_listener.dart';
import 'package:app_flowy/plugins/doc/application/doc_service.dart';
import 'package:app_flowy/plugins/document/application/doc_service.dart';
import 'package:appflowy_editor/appflowy_editor.dart'
show EditorState, Document, Transaction;
import 'package:flowy_sdk/protobuf/flowy-folder/trash.pb.dart';
@@ -17,19 +17,19 @@ part 'doc_bloc.freezed.dart';
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
final ViewPB view;
final DocumentService service;
final DocumentService _documentService;
final ViewListener listener;
final TrashService trashService;
final ViewListener _listener;
final TrashService _trashService;
late EditorState editorState;
StreamSubscription? _subscription;
DocumentBloc({
required this.view,
required this.service,
required this.listener,
required this.trashService,
}) : super(DocumentState.initial()) {
}) : _documentService = DocumentService(),
_listener = ViewListener(view: view),
_trashService = TrashService(),
super(DocumentState.initial()) {
on<DocumentEvent>((event, emit) async {
await event.map(
initial: (Initial value) async {
@@ -43,7 +43,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
emit(state.copyWith(isDeleted: false));
},
deletePermanently: (DeletePermanently value) async {
final result = await trashService
final result = await _trashService
.deleteViews([Tuple2(view.id, TrashType.TrashView)]);
final newState = result.fold(
@@ -51,7 +51,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
emit(newState);
},
restorePage: (RestorePage value) async {
final result = await trashService.putback(view.id);
final result = await _trashService.putback(view.id);
final newState = result.fold(
(l) => state.copyWith(isDeleted: false), (r) => state);
emit(newState);
@@ -62,18 +62,18 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
@override
Future<void> close() async {
await listener.stop();
await _listener.stop();
if (_subscription != null) {
await _subscription?.cancel();
}
await service.closeDocument(docId: view.id);
await _documentService.closeDocument(docId: view.id);
return super.close();
}
Future<void> _initial(Initial value, Emitter<DocumentState> emit) async {
final result = await service.openDocument(view: view);
final result = await _documentService.openDocument(view: view);
result.fold(
(block) {
final document = Document.fromJson(jsonDecode(block.snapshot));
@@ -96,7 +96,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
}
void _listenOnViewChange() {
listener.start(
_listener.start(
onViewDeleted: (result) {
result.fold(
(view) => add(const DocumentEvent.deleted()),
@@ -115,7 +115,9 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
void _listenOnDocumentChange() {
_subscription = editorState.transactionStream.listen((transaction) {
final json = jsonEncode(TransactionAdaptor(transaction).toJson());
service.applyEdit(docId: view.id, operations: json).then((result) {
_documentService
.applyEdit(docId: view.id, operations: json)
.then((result) {
result.fold(
(l) => null,
(err) => Log.error(err),
@@ -1,14 +1,14 @@
import 'dart:convert';
import 'dart:io';
import 'package:app_flowy/plugins/doc/application/share_service.dart';
import 'package:app_flowy/workspace/application/markdown/document_markdown.dart';
import 'package:app_flowy/plugins/document/application/share_service.dart';
import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_editor/appflowy_editor.dart' show Document;
import 'package:appflowy_editor/appflowy_editor.dart'
show Document, documentToMarkdown;
part 'share_bloc.freezed.dart';
class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
@@ -4,8 +4,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/util.dart';
import 'package:app_flowy/startup/plugin/plugin.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/plugins/doc/application/share_bloc.dart';
import 'package:app_flowy/plugins/document/application/share_bloc.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:app_flowy/workspace/presentation/home/toast.dart';
import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
@@ -15,8 +14,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:clipboard/clipboard.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
@@ -24,7 +21,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'document_page.dart';
@@ -41,6 +37,9 @@ class DocumentPluginBuilder extends PluginBuilder {
@override
String get menuName => LocaleKeys.document_menuName.tr();
@override
String get menuIcon => "editor/documents";
@override
PluginType get pluginType => PluginType.editor;
@@ -129,23 +128,13 @@ class DocumentShareButton extends StatelessWidget {
);
},
child: BlocBuilder<DocShareBloc, DocShareState>(
builder: (context, state) {
return ChangeNotifierProvider.value(
value: Provider.of<AppearanceSetting>(context, listen: true),
child: Selector<AppearanceSetting, Locale>(
selector: (ctx, notifier) => notifier.locale,
builder: (ctx, _, child) => ConstrainedBox(
constraints: const BoxConstraints.expand(
height: 30,
width: 100,
),
child: ShareActionList(
view: view,
),
),
),
);
},
builder: (context, state) => ConstrainedBox(
constraints: const BoxConstraints.expand(
height: 30,
width: 100,
),
child: ShareActionList(view: view),
),
),
),
);
@@ -177,7 +166,6 @@ class ShareActionList extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final docShareBloc = context.read<DocShareBloc>();
return PopoverActionList<ShareActionWrapper>(
direction: PopoverDirection.bottomWithCenterAligned,
@@ -187,9 +175,6 @@ class ShareActionList extends StatelessWidget {
buildChild: (controller) {
return RoundedTextButton(
title: LocaleKeys.shareAction_buttonText.tr(),
fontSize: 12,
borderRadius: Corners.s6Border,
color: theme.main1,
onPressed: () => controller.show(),
);
},
@@ -228,7 +213,7 @@ class ShareActionWrapper extends ActionCell {
ShareActionWrapper(this.inner);
@override
Widget? icon(Color iconColor) => null;
Widget? leftIcon(Color iconColor) => null;
@override
String get name => inner.name;
@@ -1,7 +1,7 @@
import 'package:app_flowy/plugins/doc/editor_styles.dart';
import 'package:app_flowy/plugins/doc/presentation/plugins/horizontal_rule_node_widget.dart';
import 'package:app_flowy/plugins/document/editor_styles.dart';
import 'package:app_flowy/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/doc/presentation/banner.dart';
import 'package:app_flowy/plugins/document/presentation/banner.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
@@ -30,7 +30,7 @@ class _DocumentPageState extends State<DocumentPage> {
@override
void initState() {
// The appflowy editor use Intl as locatization, set the default language as fallback.
// The appflowy editor use Intl as localization, set the default language as fallback.
Intl.defaultLocale = 'en_US';
documentBloc = getIt<DocumentBloc>(param1: super.widget.view)
..add(const DocumentEvent.initial());
@@ -77,7 +77,9 @@ class _DocumentPageState extends State<DocumentPage> {
children: [
if (state.isDeleted) _renderBanner(context),
// AppFlowy Editor
_renderAppFlowyEditor(context.read<DocumentBloc>().editorState),
_renderAppFlowyEditor(
context.read<DocumentBloc>().editorState,
),
],
);
}
@@ -93,19 +95,31 @@ class _DocumentPageState extends State<DocumentPage> {
}
Widget _renderAppFlowyEditor(EditorState editorState) {
final theme = Theme.of(context);
final editorMaxWidth = MediaQuery.of(context).size.width * 0.6;
final editor = AppFlowyEditor(
editorState: editorState,
editorStyle: customEditorStyle(context),
autoFocus: editorState.document.isEmpty,
customBuilders: {
'horizontal_rule': HorizontalRuleWidgetBuilder(),
},
shortcutEvents: [
insertHorizontalRule,
],
themeData: theme.copyWith(extensions: [
...theme.extensions.values,
customEditorTheme(context),
...customPluginTheme(context),
]),
);
return Expanded(
child: SizedBox.expand(
child: editor,
child: Center(
child: Container(
constraints: BoxConstraints(
maxWidth: editorMaxWidth,
),
child: editor,
),
),
);
}
@@ -0,0 +1,66 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
const _baseFontSize = 14.0;
EditorStyle customEditorTheme(BuildContext context) {
var editorStyle = Theme.of(context).brightness == Brightness.dark
? EditorStyle.dark
: EditorStyle.light;
editorStyle = editorStyle.copyWith(
padding: const EdgeInsets.all(0),
textStyle: editorStyle.textStyle?.copyWith(
fontFamily: 'poppins',
fontSize: _baseFontSize,
),
placeholderTextStyle: editorStyle.placeholderTextStyle?.copyWith(
fontFamily: 'poppins',
fontSize: _baseFontSize,
),
bold: editorStyle.bold?.copyWith(
fontWeight: FontWeight.w500,
),
backgroundColor: Theme.of(context).colorScheme.surface,
);
return editorStyle;
}
Iterable<ThemeExtension<dynamic>> customPluginTheme(BuildContext context) {
const basePadding = 12.0;
var headingPluginStyle = Theme.of(context).brightness == Brightness.dark
? HeadingPluginStyle.dark
: HeadingPluginStyle.light;
headingPluginStyle = headingPluginStyle.copyWith(
textStyle: (EditorState editorState, Node node) {
final headingToFontSize = {
'h1': _baseFontSize + 12,
'h2': _baseFontSize + 8,
'h3': _baseFontSize + 4,
'h4': _baseFontSize,
'h5': _baseFontSize,
'h6': _baseFontSize,
};
final fontSize =
headingToFontSize[node.attributes.heading] ?? _baseFontSize;
return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600);
},
padding: (EditorState editorState, Node node) {
final headingToPadding = {
'h1': basePadding + 6,
'h2': basePadding + 4,
'h3': basePadding + 2,
'h4': basePadding,
'h5': basePadding,
'h6': basePadding,
};
final padding = headingToPadding[node.attributes.heading] ?? basePadding;
return EdgeInsets.only(bottom: padding);
},
);
final pluginTheme = Theme.of(context).brightness == Brightness.dark
? darkPlguinStyleExtension
: lightPlguinStyleExtension;
return pluginTheme.toList()
..removeWhere((element) => element is HeadingPluginStyle)
..add(headingPluginStyle);
}
@@ -1,11 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/buttons/base_styled_button.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
class DocumentBanner extends StatelessWidget {
@@ -17,12 +15,11 @@ class DocumentBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 60),
child: Container(
width: double.infinity,
color: theme.main1,
color: Theme.of(context).colorScheme.primary,
child: FittedBox(
alignment: Alignment.center,
fit: BoxFit.scaleDown,
@@ -36,30 +33,32 @@ class DocumentBanner extends StatelessWidget {
minHeight: 40,
contentPadding: EdgeInsets.zero,
bgColor: Colors.transparent,
hoverColor: theme.main2,
downColor: theme.main1,
hoverColor: Theme.of(context).colorScheme.primary,
downColor: Theme.of(context).colorScheme.primaryContainer,
outlineColor: Colors.white,
borderRadius: Corners.s8Border,
onPressed: onRestore,
child: FlowyText.medium(
LocaleKeys.deletePagePrompt_restore.tr(),
color: Colors.white,
fontSize: 14)),
LocaleKeys.deletePagePrompt_restore.tr(),
color: Theme.of(context).colorScheme.onPrimary,
fontSize: 14,
)),
const HSpace(20),
BaseStyledButton(
minWidth: 220,
minHeight: 40,
contentPadding: EdgeInsets.zero,
bgColor: Colors.transparent,
hoverColor: theme.main2,
downColor: theme.main1,
hoverColor: Theme.of(context).colorScheme.primaryContainer,
downColor: Theme.of(context).colorScheme.primary,
outlineColor: Colors.white,
borderRadius: Corners.s8Border,
onPressed: onDelete,
child: FlowyText.medium(
LocaleKeys.deletePagePrompt_deletePermanent.tr(),
color: Colors.white,
fontSize: 14)),
LocaleKeys.deletePagePrompt_deletePermanent.tr(),
color: Theme.of(context).colorScheme.onPrimary,
fontSize: 14,
)),
],
),
),
@@ -38,9 +38,11 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
name: () => 'Horizontal rule',
icon: const Icon(
icon: (editorState, onSelected) => Icon(
Icons.horizontal_rule,
color: Colors.black,
color: onSelected
? editorState.editorStyle.selectionMenuItemSelectedIconColor
: editorState.editorStyle.selectionMenuItemIconColor,
size: 18.0,
),
keywords: ['horizontal rule'],
@@ -13,7 +13,7 @@ class GridBlockCache {
late GridRowCache _rowCache;
late GridBlockListener _listener;
List<RowInfo> get rows => _rowCache.rows;
List<RowInfo> get rows => _rowCache.visibleRows;
GridRowCache get rowCache => _rowCache;
GridBlockCache({
@@ -30,7 +30,7 @@ class GridBlockCache {
_listener = GridBlockListener(blockId: block.id);
_listener.start((result) {
result.fold(
(changesets) => _rowCache.applyChangesets(changesets),
(changeset) => _rowCache.applyChangesets(changeset),
(err) => Log.error(err),
);
});
@@ -7,11 +7,12 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
typedef GridBlockUpdateNotifierValue = Either<List<GridBlockChangesetPB>, FlowyError>;
typedef GridBlockUpdateNotifierValue = Either<GridBlockChangesetPB, FlowyError>;
class GridBlockListener {
final String blockId;
PublishNotifier<GridBlockUpdateNotifierValue>? _rowsUpdateNotifier = PublishNotifier();
PublishNotifier<GridBlockUpdateNotifierValue>? _rowsUpdateNotifier =
PublishNotifier();
GridNotificationListener? _listener;
GridBlockListener({required this.blockId});
@@ -29,11 +30,12 @@ class GridBlockListener {
_rowsUpdateNotifier?.addPublishListener(onBlockChanged);
}
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
void _handler(GridDartNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.DidUpdateGridBlock:
case GridDartNotification.DidUpdateGridBlock:
result.fold(
(payload) => _rowsUpdateNotifier?.value = left([GridBlockChangesetPB.fromBuffer(payload)]),
(payload) => _rowsUpdateNotifier?.value =
left(GridBlockChangesetPB.fromBuffer(payload)),
(error) => _rowsUpdateNotifier?.value = right(error),
);
break;
@@ -22,9 +22,9 @@ class CellListener {
objectId: "$rowId:$fieldId", handler: _handler);
}
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
void _handler(GridDartNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.DidUpdateCell:
case GridDartNotification.DidUpdateCell:
result.fold(
(payload) => _updateCellNotifier?.value = left(unit),
(error) => _updateCellNotifier?.value = right(error),
@@ -150,22 +150,22 @@ class IGridCellController<T, D> extends Equatable {
_fieldNotifier = fieldNotifier,
_fieldService = FieldService(
gridId: cellId.gridId,
fieldId: cellId.fieldContext.id,
fieldId: cellId.fieldInfo.id,
),
_cacheKey = GridCellCacheKey(
rowId: cellId.rowId,
fieldId: cellId.fieldContext.id,
fieldId: cellId.fieldInfo.id,
);
String get gridId => cellId.gridId;
String get rowId => cellId.rowId;
String get fieldId => cellId.fieldContext.id;
String get fieldId => cellId.fieldInfo.id;
GridFieldContext get fieldContext => cellId.fieldContext;
FieldInfo get fieldInfo => cellId.fieldInfo;
FieldType get fieldType => cellId.fieldContext.fieldType;
FieldType get fieldType => cellId.fieldInfo.fieldType;
VoidCallback? startListening({
required void Function(T?) onCellChanged,
@@ -179,7 +179,7 @@ class IGridCellController<T, D> extends Equatable {
_cellDataNotifier = ValueNotifier(_cellsCache.get(_cacheKey));
_cellListener =
CellListener(rowId: cellId.rowId, fieldId: cellId.fieldContext.id);
CellListener(rowId: cellId.rowId, fieldId: cellId.fieldInfo.id);
/// 1.Listen on user edit event and load the new cell data if needed.
/// For example:
@@ -234,7 +234,7 @@ class IGridCellController<T, D> extends Equatable {
return data;
}
/// Return the FieldTypeOptionDataPB that can be parsed into corresponding class using the [parser].
/// Return the TypeOptionPB that can be parsed into corresponding class using the [parser].
/// [PD] is the type that the parser return.
Future<Either<PD, FlowyError>>
getFieldTypeOption<PD, P extends TypeOptionDataParser>(P parser) {
@@ -290,50 +290,53 @@ class IGridCellController<T, D> extends Equatable {
});
}
void dispose() {
Future<void> dispose() async {
if (_isDispose) {
Log.error("$this should only dispose once");
return;
}
_isDispose = true;
_cellListener?.stop();
await _cellListener?.stop();
_loadDataOperation?.cancel();
_saveDataOperation?.cancel();
_cellDataNotifier = null;
if (_onFieldChangedFn != null) {
_fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!);
_fieldNotifier.dispose();
await _fieldNotifier.dispose();
_onFieldChangedFn = null;
}
}
@override
List<Object> get props =>
[_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.fieldContext.id];
[_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.fieldInfo.id];
}
class GridCellFieldNotifierImpl extends IGridCellFieldNotifier {
final GridFieldController _cache;
OnChangeset? _onChangesetFn;
final GridFieldController _fieldController;
OnReceiveUpdateFields? _onChangesetFn;
GridCellFieldNotifierImpl(GridFieldController cache) : _cache = cache;
GridCellFieldNotifierImpl(GridFieldController cache)
: _fieldController = cache;
@override
void onCellDispose() {
if (_onChangesetFn != null) {
_cache.removeListener(onChangesetListener: _onChangesetFn!);
_fieldController.removeListener(onChangesetListener: _onChangesetFn!);
_onChangesetFn = null;
}
}
@override
void onCellFieldChanged(void Function(FieldPB p1) callback) {
_onChangesetFn = (FieldChangesetPB changeset) {
for (final updatedField in changeset.updatedFields) {
callback(updatedField);
void onCellFieldChanged(void Function(FieldInfo) callback) {
_onChangesetFn = (List<FieldInfo> filedInfos) {
for (final field in filedInfos) {
callback(field);
}
};
_cache.addListener(onChangeset: _onChangesetFn);
_fieldController.addListener(
onFieldsUpdated: _onChangesetFn,
);
}
}
@@ -25,7 +25,7 @@ class GridCellDataLoader<T> {
final fut = service.getCell(cellId: cellId);
return fut.then(
(result) => result.fold(
(GridCellPB cell) {
(CellPB cell) {
try {
return parser.parserData(cell.data);
} catch (e, s) {
@@ -29,10 +29,12 @@ class CellDataPersistence implements IGridCellDataPersistence<String> {
@freezed
class CalendarData with _$CalendarData {
const factory CalendarData({required DateTime date, String? time}) = _CalendarData;
const factory CalendarData({required DateTime date, String? time}) =
_CalendarData;
}
class DateCellDataPersistence implements IGridCellDataPersistence<CalendarData> {
class DateCellDataPersistence
implements IGridCellDataPersistence<CalendarData> {
final GridCellIdentifier cellId;
DateCellDataPersistence({
required this.cellId,
@@ -40,10 +42,11 @@ class DateCellDataPersistence implements IGridCellDataPersistence<CalendarData>
@override
Future<Option<FlowyError>> save(CalendarData data) {
var payload = DateChangesetPayloadPB.create()..cellIdentifier = _makeCellIdPayload(cellId);
var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId);
final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
payload.date = date;
payload.isUtc = data.date.isUtc;
if (data.time != null) {
payload.time = data.time!;
@@ -58,8 +61,8 @@ class DateCellDataPersistence implements IGridCellDataPersistence<CalendarData>
}
}
GridCellIdPB _makeCellIdPayload(GridCellIdentifier cellId) {
return GridCellIdPB.create()
CellPathPB _makeCellPath(GridCellIdentifier cellId) {
return CellPathPB.create()
..gridId = cellId.gridId
..fieldId = cellId.fieldId
..rowId = cellId.rowId;
@@ -1,10 +1,10 @@
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:flutter/foundation.dart';
import 'cell_service.dart';
abstract class IGridCellFieldNotifier {
void onCellFieldChanged(void Function(FieldPB) callback);
void onCellFieldChanged(void Function(FieldInfo) callback);
void onCellDispose();
}
@@ -42,10 +42,10 @@ class CellService {
return GridEventUpdateCell(payload).send();
}
Future<Either<GridCellPB, FlowyError>> getCell({
Future<Either<CellPB, FlowyError>> getCell({
required GridCellIdentifier cellId,
}) {
final payload = GridCellIdPB.create()
final payload = CellPathPB.create()
..gridId = cellId.gridId
..fieldId = cellId.fieldId
..rowId = cellId.rowId;
@@ -60,17 +60,17 @@ class GridCellIdentifier with _$GridCellIdentifier {
const factory GridCellIdentifier({
required String gridId,
required String rowId,
required GridFieldContext fieldContext,
required FieldInfo fieldInfo,
}) = _GridCellIdentifier;
// ignore: unused_element
const GridCellIdentifier._();
String get fieldId => fieldContext.id;
String get fieldId => fieldInfo.id;
FieldType get fieldType => fieldContext.fieldType;
FieldType get fieldType => fieldInfo.fieldType;
ValueKey key() {
return ValueKey("$rowId$fieldId${fieldContext.fieldType}");
return ValueKey("$rowId$fieldId${fieldInfo.fieldType}");
}
}
@@ -37,7 +37,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -139,7 +139,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -176,7 +176,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
final result = await FieldService.updateFieldTypeOption(
gridId: cellController.gridId,
fieldId: cellController.fieldContext.id,
fieldId: cellController.fieldInfo.id,
typeOptionData: newDateTypeOption.writeToBuffer(),
);
@@ -31,7 +31,7 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -58,14 +58,14 @@ class DateCellState with _$DateCellState {
const factory DateCellState({
required DateCellDataPB? data,
required String dateStr,
required GridFieldContext fieldContext,
required FieldInfo fieldInfo,
}) = _DateCellState;
factory DateCellState.initial(GridDateCellController context) {
final cellData = context.getCellData();
return DateCellState(
fieldContext: context.fieldContext,
fieldInfo: context.fieldInfo,
data: cellData,
dateStr: _dateStrFromCellData(cellData),
);
@@ -46,7 +46,7 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -36,7 +36,7 @@ class SelectOptionCellBloc
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -81,7 +81,7 @@ class SelectOptionCellEditorBloc
@override
Future<void> close() async {
_delayOperation?.cancel();
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -11,7 +11,7 @@ class SelectOptionService {
SelectOptionService({required this.cellId});
String get gridId => cellId.gridId;
String get fieldId => cellId.fieldContext.id;
String get fieldId => cellId.fieldInfo.id;
String get rowId => cellId.rowId;
Future<Either<Unit, FlowyError>> create({required String name}) {
@@ -21,11 +21,11 @@ class SelectOptionService {
(result) {
return result.fold(
(option) {
final cellIdentifier = GridCellIdPB.create()
final cellIdentifier = CellPathPB.create()
..gridId = gridId
..fieldId = fieldId
..rowId = rowId;
final payload = SelectOptionChangesetPayloadPB.create()
final payload = SelectOptionChangesetPB.create()
..insertOptions.add(option)
..cellIdentifier = cellIdentifier;
return GridEventUpdateSelectOption(payload).send();
@@ -39,7 +39,7 @@ class SelectOptionService {
Future<Either<Unit, FlowyError>> update({
required SelectOptionPB option,
}) {
final payload = SelectOptionChangesetPayloadPB.create()
final payload = SelectOptionChangesetPB.create()
..updateOptions.add(option)
..cellIdentifier = _cellIdentifier();
return GridEventUpdateSelectOption(payload).send();
@@ -47,7 +47,7 @@ class SelectOptionService {
Future<Either<Unit, FlowyError>> delete(
{required Iterable<SelectOptionPB> options}) {
final payload = SelectOptionChangesetPayloadPB.create()
final payload = SelectOptionChangesetPB.create()
..deleteOptions.addAll(options)
..cellIdentifier = _cellIdentifier();
@@ -55,7 +55,7 @@ class SelectOptionService {
}
Future<Either<SelectOptionCellDataPB, FlowyError>> getOptionContext() {
final payload = GridCellIdPB.create()
final payload = CellPathPB.create()
..gridId = gridId
..fieldId = fieldId
..rowId = rowId;
@@ -65,7 +65,7 @@ class SelectOptionService {
Future<Either<void, FlowyError>> select(
{required Iterable<String> optionIds}) {
final payload = SelectOptionCellChangesetPayloadPB.create()
final payload = SelectOptionCellChangesetPB.create()
..cellIdentifier = _cellIdentifier()
..insertOptionIds.addAll(optionIds);
return GridEventUpdateSelectOptionCell(payload).send();
@@ -73,14 +73,14 @@ class SelectOptionService {
Future<Either<void, FlowyError>> unSelect(
{required Iterable<String> optionIds}) {
final payload = SelectOptionCellChangesetPayloadPB.create()
final payload = SelectOptionCellChangesetPB.create()
..cellIdentifier = _cellIdentifier()
..deleteOptionIds.addAll(optionIds);
return GridEventUpdateSelectOptionCell(payload).send();
}
GridCellIdPB _cellIdentifier() {
return GridCellIdPB.create()
CellPathPB _cellIdentifier() {
return CellPathPB.create()
..gridId = gridId
..fieldId = fieldId
..rowId = rowId;
@@ -35,7 +35,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -38,7 +38,7 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -36,7 +36,7 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
await cellController.dispose();
return super.close();
}
@@ -18,7 +18,7 @@ class FieldActionSheetBloc
),
super(
FieldActionSheetState.initial(
FieldTypeOptionDataPB.create()..field_2 = fieldCellContext.field,
TypeOptionPB.create()..field_2 = fieldCellContext.field,
),
) {
on<FieldActionSheetEvent>(
@@ -85,12 +85,12 @@ class FieldActionSheetEvent with _$FieldActionSheetEvent {
@freezed
class FieldActionSheetState with _$FieldActionSheetState {
const factory FieldActionSheetState({
required FieldTypeOptionDataPB fieldTypeOptionData,
required TypeOptionPB fieldTypeOptionData,
required String errorText,
required String fieldName,
}) = _FieldActionSheetState;
factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) =>
factory FieldActionSheetState.initial(TypeOptionPB data) =>
FieldActionSheetState(
fieldTypeOptionData: data,
errorText: '',
@@ -1,22 +1,26 @@
import 'dart:collection';
import 'package:app_flowy/plugins/grid/application/field/grid_listener.dart';
import 'package:app_flowy/plugins/grid/application/filter/filter_listener.dart';
import 'package:app_flowy/plugins/grid/application/filter/filter_service.dart';
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
import 'package:app_flowy/plugins/grid/application/setting/setting_listener.dart';
import 'package:app_flowy/plugins/grid/application/setting/setting_service.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
import 'package:flutter/foundation.dart';
import '../row/row_cache.dart';
class _GridFieldNotifier extends ChangeNotifier {
List<GridFieldContext> _fieldContexts = [];
List<FieldInfo> _fieldInfos = [];
set fieldContexts(List<GridFieldContext> fieldContexts) {
_fieldContexts = fieldContexts;
set fieldInfos(List<FieldInfo> fieldInfos) {
_fieldInfos = fieldInfos;
notifyListeners();
}
@@ -24,91 +28,225 @@ class _GridFieldNotifier extends ChangeNotifier {
notifyListeners();
}
List<GridFieldContext> get fieldContexts => _fieldContexts;
List<FieldInfo> get fieldInfos => _fieldInfos;
}
typedef OnChangeset = void Function(FieldChangesetPB);
typedef OnReceiveFields = void Function(List<GridFieldContext>);
class _GridFilterNotifier extends ChangeNotifier {
List<FilterInfo> _filters = [];
set filters(List<FilterInfo> filters) {
_filters = filters;
notifyListeners();
}
void notify() {
notifyListeners();
}
List<FilterInfo> get filters => _filters;
}
typedef OnReceiveUpdateFields = void Function(List<FieldInfo>);
typedef OnReceiveFields = void Function(List<FieldInfo>);
typedef OnReceiveFilters = void Function(List<FilterInfo>);
class GridFieldController {
final String gridId;
// Listeners
final GridFieldsListener _fieldListener;
final SettingListener _settingListener;
final Map<OnReceiveFields, VoidCallback> _fieldCallbackMap = {};
final Map<OnChangeset, OnChangeset> _changesetCallbackMap = {};
final FiltersListener _filterListener;
// FFI services
final GridFFIService _gridFFIService;
final SettingFFIService _settingFFIService;
final FilterFFIService _filterFFIService;
// Field callbacks
final Map<OnReceiveFields, VoidCallback> _fieldCallbacks = {};
_GridFieldNotifier? _fieldNotifier = _GridFieldNotifier();
final Map<String, GridGroupConfigurationPB> _configurationByFieldId = {};
List<GridFieldContext> get fieldContexts =>
[..._fieldNotifier?.fieldContexts ?? []];
// Field updated callbacks
final Map<OnReceiveUpdateFields, void Function(List<FieldInfo>)>
_updatedFieldCallbacks = {};
// Group callbacks
final Map<String, GroupConfigurationPB> _groupConfigurationByFieldId = {};
// Filter callbacks
final Map<OnReceiveFilters, VoidCallback> _filterCallbacks = {};
_GridFilterNotifier? _filterNotifier = _GridFilterNotifier();
final Map<String, FilterPB> _filterPBByFieldId = {};
// Getters
List<FieldInfo> get fieldInfos => [..._fieldNotifier?.fieldInfos ?? []];
List<FilterInfo> get filterInfos => [..._filterNotifier?.filters ?? []];
FieldInfo? getField(String fieldId) {
final fields = _fieldNotifier?.fieldInfos
.where((element) => element.id == fieldId)
.toList() ??
[];
if (fields.isEmpty) {
return null;
}
assert(fields.length == 1);
return fields.first;
}
FilterInfo? getFilter(String filterId) {
final filters = _filterNotifier?.filters
.where((element) => element.filter.id == filterId)
.toList() ??
[];
if (filters.isEmpty) {
return null;
}
assert(filters.length == 1);
return filters.first;
}
GridFieldController({required this.gridId})
: _fieldListener = GridFieldsListener(gridId: gridId),
_settingListener = SettingListener(gridId: gridId),
_filterListener = FiltersListener(viewId: gridId),
_gridFFIService = GridFFIService(gridId: gridId),
_settingFFIService = SettingFFIService(viewId: gridId),
_settingListener = SettingListener(gridId: gridId) {
_filterFFIService = FilterFFIService(viewId: gridId),
_settingFFIService = SettingFFIService(viewId: gridId) {
//Listen on field's changes
_listenOnFieldChanges();
//Listen on setting changes
_listenOnSettingChanges();
//Listen on the fitler changes
_listenOnFilterChanges();
_settingFFIService.getSetting().then((result) {
result.fold(
(setting) => _updateSettingConfiguration(setting),
(err) => Log.error(err),
);
});
}
void _listenOnFilterChanges() {
//Listen on the fitler changes
_filterListener.start(onFilterChanged: (result) {
result.fold(
(changeset) {
final List<FilterInfo> filters = filterInfos;
// Deletes the filters
final deleteFilterIds =
changeset.deleteFilters.map((e) => e.id).toList();
if (deleteFilterIds.isNotEmpty) {
filters.retainWhere(
(element) => !deleteFilterIds.contains(element.filter.id),
);
_filterPBByFieldId.removeWhere(
(key, value) => deleteFilterIds.contains(value.id));
}
// Inserts the new filter if it's not exist
for (final newFilter in changeset.insertFilters) {
final filterIndex = filters
.indexWhere((element) => element.filter.id == newFilter.id);
if (filterIndex == -1) {
final fieldInfo = _findFieldInfoForFilter(fieldInfos, newFilter);
if (fieldInfo != null) {
_filterPBByFieldId[fieldInfo.id] = newFilter;
filters.add(FilterInfo(gridId, newFilter, fieldInfo));
}
}
}
for (final updatedFilter in changeset.updateFilters) {
final filterIndex = filters.indexWhere(
(element) => element.filter.id == updatedFilter.filterId,
);
// Remove the old filter
if (filterIndex != -1) {
filters.removeAt(filterIndex);
_filterPBByFieldId.removeWhere(
(key, value) => value.id == updatedFilter.filterId);
}
// Insert the filter if there is a fitler and its field info is
// not null
if (updatedFilter.hasFilter()) {
final fieldInfo = _findFieldInfoForFilter(
fieldInfos,
updatedFilter.filter,
);
if (fieldInfo != null) {
// Insert the filter with the position: filterIndex, otherwise,
// append it to the end of the list.
final filterInfo =
FilterInfo(gridId, updatedFilter.filter, fieldInfo);
if (filterIndex != -1) {
filters.insert(filterIndex, filterInfo);
} else {
filters.add(filterInfo);
}
_filterPBByFieldId[fieldInfo.id] = updatedFilter.filter;
}
}
}
_updateFieldInfos();
_filterNotifier?.filters = filters;
},
(err) => Log.error(err),
);
});
}
void _listenOnSettingChanges() {
//Listen on setting changes
_settingListener.start(onSettingUpdated: (result) {
result.fold(
(setting) => _updateSettingConfiguration(setting),
(r) => Log.error(r),
);
});
}
void _listenOnFieldChanges() {
//Listen on field's changes
_fieldListener.start(onFieldsChanged: (result) {
result.fold(
(changeset) {
_deleteFields(changeset.deletedFields);
_insertFields(changeset.insertedFields);
_updateFields(changeset.updatedFields);
for (final listener in _changesetCallbackMap.values) {
listener(changeset);
final updateFields = _updateFields(changeset.updatedFields);
for (final listener in _updatedFieldCallbacks.values) {
listener(updateFields);
}
},
(err) => Log.error(err),
);
});
//Listen on setting changes
_settingListener.start(onSettingUpdated: (result) {
result.fold(
(setting) => _updateGroupConfiguration(setting),
(r) => Log.error(r),
);
});
_settingFFIService.getSetting().then((result) {
result.fold(
(setting) => _updateGroupConfiguration(setting),
(err) => Log.error(err),
);
});
}
GridFieldContext? getField(String fieldId) {
final fields = _fieldNotifier?.fieldContexts
.where(
(element) => element.id == fieldId,
)
.toList();
if (fields?.isEmpty ?? true) {
return null;
}
return fields!.first;
}
void _updateGroupConfiguration(GridSettingPB setting) {
_configurationByFieldId.clear();
void _updateSettingConfiguration(GridSettingPB setting) {
_groupConfigurationByFieldId.clear();
for (final configuration in setting.groupConfigurations.items) {
_configurationByFieldId[configuration.fieldId] = configuration;
_groupConfigurationByFieldId[configuration.fieldId] = configuration;
}
_updateFieldContexts();
for (final configuration in setting.filters.items) {
_filterPBByFieldId[configuration.fieldId] = configuration;
}
_updateFieldInfos();
}
void _updateFieldContexts() {
void _updateFieldInfos() {
if (_fieldNotifier != null) {
for (var field in _fieldNotifier!.fieldContexts) {
if (_configurationByFieldId[field.id] != null) {
field._isGroupField = true;
} else {
field._isGroupField = false;
}
for (var field in _fieldNotifier!.fieldInfos) {
field._isGroupField = _groupConfigurationByFieldId[field.id] != null;
field._hasFilter = _filterPBByFieldId[field.id] != null;
}
_fieldNotifier?.notify();
}
@@ -116,20 +254,33 @@ class GridFieldController {
Future<void> dispose() async {
await _fieldListener.stop();
await _filterListener.stop();
await _settingListener.stop();
for (final callback in _fieldCallbacks.values) {
_fieldNotifier?.removeListener(callback);
}
_fieldNotifier?.dispose();
_fieldNotifier = null;
for (final callback in _filterCallbacks.values) {
_filterNotifier?.removeListener(callback);
}
_filterNotifier?.dispose();
_filterNotifier = null;
}
Future<Either<Unit, FlowyError>> loadFields(
{required List<FieldIdPB> fieldIds}) async {
Future<Either<Unit, FlowyError>> loadFields({
required List<FieldIdPB> fieldIds,
}) async {
final result = await _gridFFIService.getFields(fieldIds: fieldIds);
return Future(
() => result.fold(
(newFields) {
_fieldNotifier?.fieldContexts = newFields.items
.map((field) => GridFieldContext(field: field))
.toList();
_updateFieldContexts();
_fieldNotifier?.fieldInfos =
newFields.map((field) => FieldInfo(field: field)).toList();
_loadFilters();
_updateFieldInfos();
return left(unit);
},
(err) => right(err),
@@ -137,20 +288,43 @@ class GridFieldController {
);
}
Future<Either<Unit, FlowyError>> _loadFilters() async {
return _filterFFIService.getAllFilters().then((result) {
return result.fold(
(filterPBs) {
final List<FilterInfo> filters = [];
for (final filterPB in filterPBs) {
final fieldInfo = _findFieldInfoForFilter(fieldInfos, filterPB);
if (fieldInfo != null) {
final filterInfo = FilterInfo(gridId, filterPB, fieldInfo);
filters.add(filterInfo);
}
}
_updateFieldInfos();
_filterNotifier?.filters = filters;
return left(unit);
},
(err) => right(err),
);
});
}
void addListener({
OnReceiveFields? onFields,
OnChangeset? onChangeset,
OnReceiveUpdateFields? onFieldsUpdated,
OnReceiveFilters? onFilters,
bool Function()? listenWhen,
}) {
if (onChangeset != null) {
callback(c) {
if (onFieldsUpdated != null) {
callback(List<FieldInfo> updateFields) {
if (listenWhen != null && listenWhen() == false) {
return;
}
onChangeset(c);
onFieldsUpdated(updateFields);
}
_changesetCallbackMap[onChangeset] = callback;
_updatedFieldCallbacks[onFieldsUpdated] = callback;
}
if (onFields != null) {
@@ -158,27 +332,42 @@ class GridFieldController {
if (listenWhen != null && listenWhen() == false) {
return;
}
onFields(fieldContexts);
onFields(fieldInfos);
}
_fieldCallbackMap[onFields] = callback;
_fieldCallbacks[onFields] = callback;
_fieldNotifier?.addListener(callback);
}
if (onFilters != null) {
callback() {
if (listenWhen != null && listenWhen() == false) {
return;
}
onFilters(filterInfos);
}
_filterCallbacks[onFilters] = callback;
_filterNotifier?.addListener(callback);
}
}
void removeListener({
OnReceiveFields? onFieldsListener,
OnChangeset? onChangesetListener,
OnReceiveFilters? onFiltersListener,
OnReceiveUpdateFields? onChangesetListener,
}) {
if (onFieldsListener != null) {
final callback = _fieldCallbackMap.remove(onFieldsListener);
final callback = _fieldCallbacks.remove(onFieldsListener);
if (callback != null) {
_fieldNotifier?.removeListener(callback);
}
}
if (onChangesetListener != null) {
_changesetCallbackMap.remove(onChangesetListener);
if (onFiltersListener != null) {
final callback = _filterCallbacks.remove(onFiltersListener);
if (callback != null) {
_filterNotifier?.removeListener(callback);
}
}
}
@@ -186,58 +375,65 @@ class GridFieldController {
if (deletedFields.isEmpty) {
return;
}
final List<GridFieldContext> newFields = fieldContexts;
final List<FieldInfo> newFields = fieldInfos;
final Map<String, FieldIdPB> deletedFieldMap = {
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
};
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
_fieldNotifier?.fieldContexts = newFields;
_fieldNotifier?.fieldInfos = newFields;
}
void _insertFields(List<IndexFieldPB> insertedFields) {
if (insertedFields.isEmpty) {
return;
}
final List<GridFieldContext> newFields = fieldContexts;
final List<FieldInfo> newFields = fieldInfos;
for (final indexField in insertedFields) {
final gridField = GridFieldContext(field: indexField.field_1);
final gridField = FieldInfo(field: indexField.field_1);
if (newFields.length > indexField.index) {
newFields.insert(indexField.index, gridField);
} else {
newFields.add(gridField);
}
}
_fieldNotifier?.fieldContexts = newFields;
_fieldNotifier?.fieldInfos = newFields;
}
void _updateFields(List<FieldPB> updatedFields) {
if (updatedFields.isEmpty) {
return;
List<FieldInfo> _updateFields(List<FieldPB> updatedFieldPBs) {
if (updatedFieldPBs.isEmpty) {
return [];
}
final List<GridFieldContext> newFields = fieldContexts;
for (final updatedField in updatedFields) {
final List<FieldInfo> newFields = fieldInfos;
final List<FieldInfo> updatedFields = [];
for (final updatedFieldPB in updatedFieldPBs) {
final index =
newFields.indexWhere((field) => field.id == updatedField.id);
newFields.indexWhere((field) => field.id == updatedFieldPB.id);
if (index != -1) {
newFields.removeAt(index);
final gridField = GridFieldContext(field: updatedField);
newFields.insert(index, gridField);
final fieldInfo = FieldInfo(field: updatedFieldPB);
newFields.insert(index, fieldInfo);
updatedFields.add(fieldInfo);
}
}
_fieldNotifier?.fieldContexts = newFields;
if (updatedFields.isNotEmpty) {
_fieldNotifier?.fieldInfos = newFields;
}
return updatedFields;
}
}
class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
final GridFieldController _cache;
OnChangeset? _onChangesetFn;
OnReceiveUpdateFields? _onChangesetFn;
OnReceiveFields? _onFieldFn;
GridRowFieldNotifierImpl(GridFieldController cache) : _cache = cache;
@override
UnmodifiableListView<GridFieldContext> get fields =>
UnmodifiableListView(_cache.fieldContexts);
UnmodifiableListView<FieldInfo> get fields =>
UnmodifiableListView(_cache.fieldInfos);
@override
void onRowFieldsChanged(VoidCallback callback) {
@@ -246,14 +442,14 @@ class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
}
@override
void onRowFieldChanged(void Function(FieldPB) callback) {
_onChangesetFn = (FieldChangesetPB changeset) {
for (final updatedField in changeset.updatedFields) {
void onRowFieldChanged(void Function(FieldInfo) callback) {
_onChangesetFn = (List<FieldInfo> fieldInfos) {
for (final updatedField in fieldInfos) {
callback(updatedField);
}
};
_cache.addListener(onChangeset: _onChangesetFn);
_cache.addListener(onFieldsUpdated: _onChangesetFn);
}
@override
@@ -270,10 +466,25 @@ class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
}
}
class GridFieldContext {
FieldInfo? _findFieldInfoForFilter(
List<FieldInfo> fieldInfos, FilterPB filter) {
final fieldIndex = fieldInfos.indexWhere((element) {
return element.id == filter.fieldId &&
element.fieldType == filter.fieldType;
});
if (fieldIndex != -1) {
return fieldInfos[fieldIndex];
} else {
return null;
}
}
class FieldInfo {
final FieldPB _field;
bool _isGroupField = false;
bool _hasFilter = false;
String get id => _field.id;
FieldType get fieldType => _field.fieldType;
@@ -290,6 +501,8 @@ class GridFieldContext {
bool get isGroupField => _isGroupField;
bool get hasFilter => _hasFilter;
bool get canGroup {
switch (_field.fieldType) {
case FieldType.Checkbox:
@@ -311,5 +524,19 @@ class GridFieldContext {
return false;
}
GridFieldContext({required FieldPB field}) : _field = field;
bool get canCreateFilter {
if (hasFilter) return false;
switch (_field.fieldType) {
case FieldType.Checkbox:
// case FieldType.MultiSelect:
case FieldType.RichText:
case FieldType.SingleSelect:
return true;
default:
return false;
}
}
FieldInfo({required FieldPB field}) : _field = field;
}
@@ -57,6 +57,9 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
},
);
},
switchToField: (FieldType fieldType) async {
await dataController.switchToField(fieldType);
},
);
},
);
@@ -73,6 +76,8 @@ class FieldEditorEvent with _$FieldEditorEvent {
const factory FieldEditorEvent.initial() = _InitialField;
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
const factory FieldEditorEvent.deleteField() = _DeleteField;
const factory FieldEditorEvent.switchToField(FieldType fieldType) =
_SwitchToField;
const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) =
_DidReceiveFieldChanged;
}
@@ -27,11 +27,11 @@ class SingleFieldListener {
}
void _handler(
GridNotification ty,
GridDartNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case GridNotification.DidUpdateField:
case GridDartNotification.DidUpdateField:
result.fold(
(payload) =>
_updateFieldNotifier?.value = left(FieldPB.fromBuffer(payload)),
@@ -34,9 +34,8 @@ class FieldService {
bool? frozen,
bool? visibility,
double? width,
List<int>? typeOptionData,
}) {
var payload = FieldChangesetPayloadPB.create()
var payload = FieldChangesetPB.create()
..gridId = gridId
..fieldId = fieldId;
@@ -60,10 +59,6 @@ class FieldService {
payload.width = width.toInt();
}
if (typeOptionData != null) {
payload.typeOptionData = typeOptionData;
}
return GridEventUpdateField(payload).send();
}
@@ -72,7 +67,7 @@ class FieldService {
required String fieldId,
required List<int> typeOptionData,
}) {
var payload = UpdateFieldTypeOptionPayloadPB.create()
var payload = TypeOptionChangesetPB.create()
..gridId = gridId
..fieldId = fieldId
..typeOptionData = typeOptionData;
@@ -96,10 +91,10 @@ class FieldService {
return GridEventDuplicateField(payload).send();
}
Future<Either<FieldTypeOptionDataPB, FlowyError>> getFieldTypeOptionData({
Future<Either<TypeOptionPB, FlowyError>> getFieldTypeOptionData({
required FieldType fieldType,
}) {
final payload = FieldTypeOptionIdPB.create()
final payload = TypeOptionPathPB.create()
..gridId = gridId
..fieldId = fieldId
..fieldType = fieldType;
@@ -25,6 +25,9 @@ class FieldTypeOptionEditBloc
didReceiveFieldUpdated: (field) {
emit(state.copyWith(field: field));
},
switchToField: (FieldType fieldType) async {
await _dataController.switchToField(fieldType);
},
);
},
);
@@ -42,6 +45,8 @@ class FieldTypeOptionEditBloc
@freezed
class FieldTypeOptionEditEvent with _$FieldTypeOptionEditEvent {
const factory FieldTypeOptionEditEvent.initial() = _Initial;
const factory FieldTypeOptionEditEvent.switchToField(FieldType fieldType) =
_SwitchToField;
const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(FieldPB field) =
_DidReceiveFieldUpdated;
}
@@ -53,8 +58,9 @@ class FieldTypeOptionEditState with _$FieldTypeOptionEditState {
}) = _FieldTypeOptionEditState;
factory FieldTypeOptionEditState.initial(
TypeOptionDataController fieldContext) =>
TypeOptionDataController typeOptionDataController,
) =>
FieldTypeOptionEditState(
field: fieldContext.field,
field: typeOptionDataController.field,
);
}
@@ -7,7 +7,7 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
typedef UpdateFieldNotifiedValue = Either<FieldChangesetPB, FlowyError>;
typedef UpdateFieldNotifiedValue = Either<GridFieldChangesetPB, FlowyError>;
class GridFieldsListener {
final String gridId;
@@ -25,12 +25,12 @@ class GridFieldsListener {
);
}
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
void _handler(GridDartNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.DidUpdateGridField:
case GridDartNotification.DidUpdateGridField:
result.fold(
(payload) => updateFieldsNotifier?.value =
left(FieldChangesetPB.fromBuffer(payload)),
left(GridFieldChangesetPB.fromBuffer(payload)),
(error) => updateFieldsNotifier?.value = right(error),
);
break;
@@ -143,11 +143,11 @@ abstract class TypeOptionFieldDelegate {
abstract class IFieldTypeOptionLoader {
String get gridId;
Future<Either<FieldTypeOptionDataPB, FlowyError>> load();
Future<Either<TypeOptionPB, FlowyError>> load();
Future<Either<Unit, FlowyError>> switchToField(
String fieldId, FieldType fieldType) {
final payload = EditFieldPayloadPB.create()
final payload = EditFieldChangesetPB.create()
..gridId = gridId
..fieldId = fieldId
..fieldType = fieldType;
@@ -158,7 +158,7 @@ abstract class IFieldTypeOptionLoader {
/// Uses when creating a new field
class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader {
FieldTypeOptionDataPB? fieldTypeOption;
TypeOptionPB? fieldTypeOption;
@override
final String gridId;
@@ -169,9 +169,9 @@ class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader {
/// Creates the field type option if the fieldTypeOption is null.
/// Otherwise, it loads the type option data from the backend.
@override
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
Future<Either<TypeOptionPB, FlowyError>> load() {
if (fieldTypeOption != null) {
final payload = FieldTypeOptionIdPB.create()
final payload = TypeOptionPathPB.create()
..gridId = gridId
..fieldId = fieldTypeOption!.field_2.id
..fieldType = fieldTypeOption!.field_2.fieldType;
@@ -207,8 +207,8 @@ class FieldTypeOptionLoader extends IFieldTypeOptionLoader {
});
@override
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
final payload = FieldTypeOptionIdPB.create()
Future<Either<TypeOptionPB, FlowyError>> load() {
final payload = TypeOptionPathPB.create()
..gridId = gridId
..fieldId = field.id
..fieldType = field.fieldType;
@@ -4,7 +4,7 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:dartz/dartz.dart';
import 'package:protobuf/protobuf.dart';
import 'package:protobuf/protobuf.dart' hide FieldInfo;
import 'package:flowy_sdk/log.dart';
import 'type_option_context.dart';
@@ -12,18 +12,24 @@ import 'type_option_context.dart';
class TypeOptionDataController {
final String gridId;
final IFieldTypeOptionLoader loader;
late FieldTypeOptionDataPB _data;
late TypeOptionPB _data;
final PublishNotifier<FieldPB> _fieldNotifier = PublishNotifier();
/// Returns a [TypeOptionDataController] used to modify the specified
/// [FieldPB]'s data
///
/// Should call [loadTypeOptionData] if the passed-in [FieldInfo]
/// is null
///
TypeOptionDataController({
required this.gridId,
required this.loader,
GridFieldContext? fieldContext,
FieldInfo? fieldInfo,
}) {
if (fieldContext != null) {
_data = FieldTypeOptionDataPB.create()
if (fieldInfo != null) {
_data = TypeOptionPB.create()
..gridId = gridId
..field_2 = fieldContext.field;
..field_2 = fieldInfo.field;
}
}
@@ -77,18 +83,17 @@ class TypeOptionDataController {
);
}
Future<void> switchToField(FieldType newFieldType) {
return loader.switchToField(field.id, newFieldType).then((result) {
return result.fold(
(_) {
// Should load the type-option data after switching to a new field.
// After loading the type-option data, the editor widget that uses
// the type-option data will be rebuild.
loadTypeOptionData();
},
(err) => Log.error(err),
);
});
Future<void> switchToField(FieldType newFieldType) async {
final result = await loader.switchToField(field.id, newFieldType);
await result.fold(
(_) {
// Should load the type-option data after switching to a new field.
// After loading the type-option data, the editor widget that uses
// the type-option data will be rebuild.
loadTypeOptionData();
},
(err) => Future(() => Log.error(err)),
);
}
void Function() addFieldListener(void Function(FieldPB) callback) {
@@ -0,0 +1,99 @@
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'filter_listener.dart';
import 'filter_service.dart';
part 'checkbox_filter_editor_bloc.freezed.dart';
class CheckboxFilterEditorBloc
extends Bloc<CheckboxFilterEditorEvent, CheckboxFilterEditorState> {
final FilterInfo filterInfo;
final FilterFFIService _ffiService;
final FilterListener _listener;
CheckboxFilterEditorBloc({required this.filterInfo})
: _ffiService = FilterFFIService(viewId: filterInfo.viewId),
_listener = FilterListener(
viewId: filterInfo.viewId,
filterId: filterInfo.filter.id,
),
super(CheckboxFilterEditorState.initial(filterInfo)) {
on<CheckboxFilterEditorEvent>(
(event, emit) async {
event.when(
initial: () async {
_startListening();
},
updateCondition: (CheckboxFilterCondition condition) {
_ffiService.insertCheckboxFilter(
filterId: filterInfo.filter.id,
fieldId: filterInfo.field.id,
condition: condition,
);
},
delete: () {
_ffiService.deleteFilter(
fieldId: filterInfo.field.id,
filterId: filterInfo.filter.id,
fieldType: filterInfo.field.fieldType,
);
},
didReceiveFilter: (FilterPB filter) {
final filterInfo = state.filterInfo.copyWith(filter: filter);
final checkboxFilter = filterInfo.checkboxFilter()!;
emit(state.copyWith(
filterInfo: filterInfo,
filter: checkboxFilter,
));
},
);
},
);
}
void _startListening() {
_listener.start(
onDeleted: () {
if (!isClosed) add(const CheckboxFilterEditorEvent.delete());
},
onUpdated: (filter) {
if (!isClosed) add(CheckboxFilterEditorEvent.didReceiveFilter(filter));
},
);
}
@override
Future<void> close() async {
await _listener.stop();
return super.close();
}
}
@freezed
class CheckboxFilterEditorEvent with _$CheckboxFilterEditorEvent {
const factory CheckboxFilterEditorEvent.initial() = _Initial;
const factory CheckboxFilterEditorEvent.didReceiveFilter(FilterPB filter) =
_DidReceiveFilter;
const factory CheckboxFilterEditorEvent.updateCondition(
CheckboxFilterCondition condition) = _UpdateCondition;
const factory CheckboxFilterEditorEvent.delete() = _Delete;
}
@freezed
class CheckboxFilterEditorState with _$CheckboxFilterEditorState {
const factory CheckboxFilterEditorState({
required FilterInfo filterInfo,
required CheckboxFilterPB filter,
}) = _GridFilterState;
factory CheckboxFilterEditorState.initial(FilterInfo filterInfo) {
return CheckboxFilterEditorState(
filterInfo: filterInfo,
filter: filterInfo.checkboxFilter()!,
);
}
}
@@ -0,0 +1,181 @@
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbenum.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbenum.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'filter_service.dart';
part 'filter_create_bloc.freezed.dart';
class GridCreateFilterBloc
extends Bloc<GridCreateFilterEvent, GridCreateFilterState> {
final String viewId;
final FilterFFIService _ffiService;
final GridFieldController fieldController;
void Function(List<FieldInfo>)? _onFieldFn;
GridCreateFilterBloc({required this.viewId, required this.fieldController})
: _ffiService = FilterFFIService(viewId: viewId),
super(GridCreateFilterState.initial(fieldController.fieldInfos)) {
on<GridCreateFilterEvent>(
(event, emit) async {
event.when(
initial: () async {
_startListening();
},
didReceiveFields: (List<FieldInfo> fields) {
emit(
state.copyWith(
allFields: fields,
creatableFields: _filterFields(fields, state.filterText),
),
);
},
didReceiveFilterText: (String text) {
emit(
state.copyWith(
filterText: text,
creatableFields: _filterFields(state.allFields, text),
),
);
},
createDefaultFilter: (FieldInfo field) {
emit(state.copyWith(didCreateFilter: true));
_createDefaultFilter(field);
},
);
},
);
}
List<FieldInfo> _filterFields(
List<FieldInfo> fields,
String filterText,
) {
final List<FieldInfo> allFields = List.from(fields);
final keyword = filterText.toLowerCase();
allFields.retainWhere((field) {
if (field.canCreateFilter) {
return false;
}
if (filterText.isNotEmpty) {
return field.name.toLowerCase().contains(keyword);
}
return true;
});
return allFields;
}
void _startListening() {
_onFieldFn = (fields) {
fields.retainWhere((field) => field.canCreateFilter);
add(GridCreateFilterEvent.didReceiveFields(fields));
};
fieldController.addListener(onFields: _onFieldFn);
}
Future<Either<Unit, FlowyError>> _createDefaultFilter(FieldInfo field) async {
final fieldId = field.id;
switch (field.fieldType) {
case FieldType.Checkbox:
return _ffiService.insertCheckboxFilter(
fieldId: fieldId,
condition: CheckboxFilterCondition.IsChecked,
);
case FieldType.DateTime:
final timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
return _ffiService.insertDateFilter(
fieldId: fieldId,
condition: DateFilterCondition.DateIs,
timestamp: timestamp,
);
case FieldType.MultiSelect:
return _ffiService.insertSelectOptionFilter(
fieldId: fieldId,
condition: SelectOptionCondition.OptionIs,
fieldType: FieldType.MultiSelect,
);
case FieldType.Number:
return _ffiService.insertNumberFilter(
fieldId: fieldId,
condition: NumberFilterCondition.Equal,
content: "",
);
case FieldType.RichText:
return _ffiService.insertTextFilter(
fieldId: fieldId,
condition: TextFilterCondition.Contains,
content: '',
);
case FieldType.SingleSelect:
return _ffiService.insertSelectOptionFilter(
fieldId: fieldId,
condition: SelectOptionCondition.OptionIs,
fieldType: FieldType.SingleSelect,
);
case FieldType.URL:
return _ffiService.insertURLFilter(
fieldId: fieldId,
condition: TextFilterCondition.Contains,
);
}
return left(unit);
}
@override
Future<void> close() async {
if (_onFieldFn != null) {
fieldController.removeListener(onFieldsListener: _onFieldFn);
_onFieldFn = null;
}
return super.close();
}
}
@freezed
class GridCreateFilterEvent with _$GridCreateFilterEvent {
const factory GridCreateFilterEvent.initial() = _Initial;
const factory GridCreateFilterEvent.didReceiveFields(List<FieldInfo> fields) =
_DidReceiveFields;
const factory GridCreateFilterEvent.createDefaultFilter(FieldInfo field) =
_CreateDefaultFilter;
const factory GridCreateFilterEvent.didReceiveFilterText(String text) =
_DidReceiveFilterText;
}
@freezed
class GridCreateFilterState with _$GridCreateFilterState {
const factory GridCreateFilterState({
required String filterText,
required List<FieldInfo> creatableFields,
required List<FieldInfo> allFields,
required bool didCreateFilter,
}) = _GridFilterState;
factory GridCreateFilterState.initial(List<FieldInfo> fields) {
return GridCreateFilterState(
filterText: "",
creatableFields: getCreatableFilter(fields),
allFields: fields,
didCreateFilter: false,
);
}
}
List<FieldInfo> getCreatableFilter(List<FieldInfo> fieldInfos) {
final List<FieldInfo> creatableFields = List.from(fieldInfos);
creatableFields.retainWhere((element) => element.canCreateFilter);
return creatableFields;
}
@@ -0,0 +1,127 @@
import 'dart:typed_data';
import 'package:app_flowy/core/grid_notification.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/filter_changeset.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
typedef UpdateFilterNotifiedValue
= Either<FilterChangesetNotificationPB, FlowyError>;
class FiltersListener {
final String viewId;
PublishNotifier<UpdateFilterNotifiedValue>? _filterNotifier =
PublishNotifier();
GridNotificationListener? _listener;
FiltersListener({required this.viewId});
void start({
required void Function(UpdateFilterNotifiedValue) onFilterChanged,
}) {
_filterNotifier?.addPublishListener(onFilterChanged);
_listener = GridNotificationListener(
objectId: viewId,
handler: _handler,
);
}
void _handler(
GridDartNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case GridDartNotification.DidUpdateFilter:
result.fold(
(payload) => _filterNotifier?.value =
left(FilterChangesetNotificationPB.fromBuffer(payload)),
(error) => _filterNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_filterNotifier?.dispose();
_filterNotifier = null;
}
}
class FilterListener {
final String viewId;
final String filterId;
PublishNotifier<FilterPB>? _onDeleteNotifier = PublishNotifier();
PublishNotifier<FilterPB>? _onUpdateNotifier = PublishNotifier();
GridNotificationListener? _listener;
FilterListener({required this.viewId, required this.filterId});
void start({
void Function()? onDeleted,
void Function(FilterPB)? onUpdated,
}) {
_onDeleteNotifier?.addPublishListener((_) {
onDeleted?.call();
});
_onUpdateNotifier?.addPublishListener((filter) {
onUpdated?.call(filter);
});
_listener = GridNotificationListener(
objectId: viewId,
handler: _handler,
);
}
void handleChangeset(FilterChangesetNotificationPB changeset) {
// check the delete filter
final deletedIndex = changeset.deleteFilters.indexWhere(
(element) => element.id == filterId,
);
if (deletedIndex != -1) {
_onDeleteNotifier?.value = changeset.deleteFilters[deletedIndex];
}
// check the updated filter
final updatedIndex = changeset.updateFilters.indexWhere(
(element) => element.filter.id == filterId,
);
if (updatedIndex != -1) {
_onUpdateNotifier?.value = changeset.updateFilters[updatedIndex].filter;
}
}
void _handler(
GridDartNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case GridDartNotification.DidUpdateFilter:
result.fold(
(payload) => handleChangeset(
FilterChangesetNotificationPB.fromBuffer(payload)),
(error) {},
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_onDeleteNotifier?.dispose();
_onDeleteNotifier = null;
_onUpdateNotifier?.dispose();
_onUpdateNotifier = null;
}
}
@@ -0,0 +1,119 @@
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
part 'filter_menu_bloc.freezed.dart';
class GridFilterMenuBloc
extends Bloc<GridFilterMenuEvent, GridFilterMenuState> {
final String viewId;
final GridFieldController fieldController;
void Function(List<FilterInfo>)? _onFilterFn;
void Function(List<FieldInfo>)? _onFieldFn;
GridFilterMenuBloc({required this.viewId, required this.fieldController})
: super(GridFilterMenuState.initial(
viewId,
fieldController.filterInfos,
fieldController.fieldInfos,
)) {
on<GridFilterMenuEvent>(
(event, emit) async {
event.when(
initial: () {
_startListening();
},
didReceiveFilters: (filters) {
emit(state.copyWith(filters: filters));
},
toggleMenu: () {
final isVisible = !state.isVisible;
emit(state.copyWith(isVisible: isVisible));
},
didReceiveFields: (List<FieldInfo> fields) {
emit(
state.copyWith(
fields: fields,
creatableFields: getCreatableFilter(fields),
),
);
},
);
},
);
}
void _startListening() {
_onFilterFn = (filters) {
add(GridFilterMenuEvent.didReceiveFilters(filters));
};
_onFieldFn = (fields) {
add(GridFilterMenuEvent.didReceiveFields(fields));
};
fieldController.addListener(
onFilters: (filters) {
_onFilterFn?.call(filters);
},
onFields: (fields) {
_onFieldFn?.call(fields);
},
);
}
@override
Future<void> close() {
if (_onFilterFn != null) {
fieldController.removeListener(onFiltersListener: _onFilterFn!);
_onFilterFn = null;
}
if (_onFieldFn != null) {
fieldController.removeListener(onFieldsListener: _onFieldFn!);
_onFieldFn = null;
}
return super.close();
}
}
@freezed
class GridFilterMenuEvent with _$GridFilterMenuEvent {
const factory GridFilterMenuEvent.initial() = _Initial;
const factory GridFilterMenuEvent.didReceiveFilters(
List<FilterInfo> filters) = _DidReceiveFilters;
const factory GridFilterMenuEvent.didReceiveFields(List<FieldInfo> fields) =
_DidReceiveFields;
const factory GridFilterMenuEvent.toggleMenu() = _SetMenuVisibility;
}
@freezed
class GridFilterMenuState with _$GridFilterMenuState {
const factory GridFilterMenuState({
required String viewId,
required List<FilterInfo> filters,
required List<FieldInfo> fields,
required List<FieldInfo> creatableFields,
required bool isVisible,
}) = _GridFilterMenuState;
factory GridFilterMenuState.initial(
String viewId,
List<FilterInfo> filterInfos,
List<FieldInfo> fields,
) =>
GridFilterMenuState(
viewId: viewId,
filters: filterInfos,
fields: fields,
creatableFields: getCreatableFilter(fields),
isVisible: false,
);
}
List<FieldInfo> getCreatableFilter(List<FieldInfo> fieldInfos) {
final List<FieldInfo> creatableFields = List.from(fieldInfos);
creatableFields.retainWhere((element) => element.canCreateFilter);
return creatableFields;
}
@@ -0,0 +1,203 @@
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
import 'package:fixnum/fixnum.dart' as $fixnum;
class FilterFFIService {
final String viewId;
const FilterFFIService({required this.viewId});
Future<Either<List<FilterPB>, FlowyError>> getAllFilters() {
final payload = GridIdPB()..value = viewId;
return GridEventGetAllFilters(payload).send().then((result) {
return result.fold(
(repeated) => left(repeated.items),
(r) => right(r),
);
});
}
Future<Either<Unit, FlowyError>> insertTextFilter({
required String fieldId,
String? filterId,
required TextFilterCondition condition,
required String content,
}) {
final filter = TextFilterPB()
..condition = condition
..content = content;
return insertFilter(
fieldId: fieldId,
filterId: filterId,
fieldType: FieldType.RichText,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> insertCheckboxFilter({
required String fieldId,
String? filterId,
required CheckboxFilterCondition condition,
}) {
final filter = CheckboxFilterPB()..condition = condition;
return insertFilter(
fieldId: fieldId,
filterId: filterId,
fieldType: FieldType.Checkbox,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> insertNumberFilter({
required String fieldId,
String? filterId,
required NumberFilterCondition condition,
String content = "",
}) {
final filter = NumberFilterPB()
..condition = condition
..content = content;
return insertFilter(
fieldId: fieldId,
filterId: filterId,
fieldType: FieldType.Number,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> insertDateFilter({
required String fieldId,
String? filterId,
required DateFilterCondition condition,
int? start,
int? end,
int? timestamp,
}) {
var filter = DateFilterPB();
if (timestamp != null) {
filter.timestamp = $fixnum.Int64(timestamp);
} else {
if (start != null && end != null) {
filter.start = $fixnum.Int64(start);
filter.end = $fixnum.Int64(end);
} else {
throw Exception(
"Start and end should not be null if the timestamp is null");
}
}
return insertFilter(
fieldId: fieldId,
filterId: filterId,
fieldType: FieldType.DateTime,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> insertURLFilter({
required String fieldId,
String? filterId,
required TextFilterCondition condition,
String content = "",
}) {
final filter = TextFilterPB()
..condition = condition
..content = content;
return insertFilter(
fieldId: fieldId,
filterId: filterId,
fieldType: FieldType.URL,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> insertSelectOptionFilter({
required String fieldId,
required FieldType fieldType,
required SelectOptionCondition condition,
String? filterId,
List<String> optionIds = const [],
}) {
final filter = SelectOptionFilterPB()
..condition = condition
..optionIds.addAll(optionIds);
return insertFilter(
fieldId: fieldId,
filterId: filterId,
fieldType: fieldType,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> insertFilter({
required String fieldId,
String? filterId,
required FieldType fieldType,
required List<int> data,
}) {
var insertFilterPayload = AlterFilterPayloadPB.create()
..fieldId = fieldId
..fieldType = fieldType
..data = data;
if (filterId != null) {
insertFilterPayload.filterId = filterId;
}
final payload = GridSettingChangesetPB.create()
..gridId = viewId
..insertFilter = insertFilterPayload;
return GridEventUpdateGridSetting(payload).send().then((result) {
return result.fold(
(l) => left(l),
(err) {
Log.error(err);
return right(err);
},
);
});
}
Future<Either<Unit, FlowyError>> deleteFilter({
required String fieldId,
required String filterId,
required FieldType fieldType,
}) {
TextFilterCondition.DoesNotContain.value;
final deleteFilterPayload = DeleteFilterPayloadPB.create()
..fieldId = fieldId
..filterId = filterId
..fieldType = fieldType;
final payload = GridSettingChangesetPB.create()
..gridId = viewId
..deleteFilter = deleteFilterPayload;
return GridEventUpdateGridSetting(payload).send().then((result) {
return result.fold(
(l) => left(l),
(err) {
Log.error(err);
return right(err);
},
);
});
}
}
@@ -0,0 +1,148 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/header/type_option/builder.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'filter_listener.dart';
import 'filter_service.dart';
part 'select_option_filter_bloc.freezed.dart';
class SelectOptionFilterEditorBloc
extends Bloc<SelectOptionFilterEditorEvent, SelectOptionFilterEditorState> {
final FilterInfo filterInfo;
final FilterFFIService _ffiService;
final FilterListener _listener;
final SingleSelectTypeOptionContext typeOptionContext;
SelectOptionFilterEditorBloc({required this.filterInfo})
: _ffiService = FilterFFIService(viewId: filterInfo.viewId),
_listener = FilterListener(
viewId: filterInfo.viewId,
filterId: filterInfo.filter.id,
),
typeOptionContext = makeSingleSelectTypeOptionContext(
gridId: filterInfo.viewId,
fieldPB: filterInfo.field.field,
),
super(SelectOptionFilterEditorState.initial(filterInfo)) {
on<SelectOptionFilterEditorEvent>(
(event, emit) async {
event.when(
initial: () async {
_startListening();
_loadOptions();
},
updateCondition: (SelectOptionCondition condition) {
_ffiService.insertSelectOptionFilter(
filterId: filterInfo.filter.id,
fieldId: filterInfo.field.id,
condition: condition,
optionIds: state.filter.optionIds,
fieldType: state.filterInfo.field.fieldType,
);
},
updateContent: (List<String> optionIds) {
_ffiService.insertSelectOptionFilter(
filterId: filterInfo.filter.id,
fieldId: filterInfo.field.id,
condition: state.filter.condition,
optionIds: optionIds,
fieldType: state.filterInfo.field.fieldType,
);
},
delete: () {
_ffiService.deleteFilter(
fieldId: filterInfo.field.id,
filterId: filterInfo.filter.id,
fieldType: filterInfo.field.fieldType,
);
},
didReceiveFilter: (FilterPB filter) {
final filterInfo = state.filterInfo.copyWith(filter: filter);
final selectOptionFilter = filterInfo.selectOptionFilter()!;
emit(state.copyWith(
filterInfo: filterInfo,
filter: selectOptionFilter,
));
},
updateFilterDescription: (String desc) {
emit(state.copyWith(filterDesc: desc));
},
);
},
);
}
void _startListening() {
_listener.start(
onDeleted: () {
if (!isClosed) add(const SelectOptionFilterEditorEvent.delete());
},
onUpdated: (filter) {
if (!isClosed) {
add(SelectOptionFilterEditorEvent.didReceiveFilter(filter));
}
},
);
}
void _loadOptions() {
typeOptionContext.loadTypeOptionData(
onCompleted: (value) {
if (!isClosed) {
String filterDesc = '';
for (final option in value.options) {
if (state.filter.optionIds.contains(option.id)) {
filterDesc += "${option.name} ";
}
}
add(SelectOptionFilterEditorEvent.updateFilterDescription(
filterDesc));
}
},
onError: (error) => Log.error(error),
);
}
@override
Future<void> close() async {
await _listener.stop();
return super.close();
}
}
@freezed
class SelectOptionFilterEditorEvent with _$SelectOptionFilterEditorEvent {
const factory SelectOptionFilterEditorEvent.initial() = _Initial;
const factory SelectOptionFilterEditorEvent.didReceiveFilter(
FilterPB filter) = _DidReceiveFilter;
const factory SelectOptionFilterEditorEvent.updateCondition(
SelectOptionCondition condition) = _UpdateCondition;
const factory SelectOptionFilterEditorEvent.updateContent(
List<String> optionIds) = _UpdateContent;
const factory SelectOptionFilterEditorEvent.updateFilterDescription(
String desc) = _UpdateDesc;
const factory SelectOptionFilterEditorEvent.delete() = _Delete;
}
@freezed
class SelectOptionFilterEditorState with _$SelectOptionFilterEditorState {
const factory SelectOptionFilterEditorState({
required FilterInfo filterInfo,
required SelectOptionFilterPB filter,
required String filterDesc,
}) = _GridFilterState;
factory SelectOptionFilterEditorState.initial(FilterInfo filterInfo) {
return SelectOptionFilterEditorState(
filterInfo: filterInfo,
filter: filterInfo.selectOptionFilter()!,
filterDesc: '',
);
}
}
@@ -0,0 +1,156 @@
import 'dart:async';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/header/type_option/builder.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'select_option_filter_list_bloc.freezed.dart';
class SelectOptionFilterListBloc<T>
extends Bloc<SelectOptionFilterListEvent, SelectOptionFilterListState> {
final SingleSelectTypeOptionContext typeOptionContext;
SelectOptionFilterListBloc({
required String viewId,
required FieldPB fieldPB,
required List<String> selectedOptionIds,
}) : typeOptionContext = makeSingleSelectTypeOptionContext(
gridId: viewId,
fieldPB: fieldPB,
),
super(SelectOptionFilterListState.initial(selectedOptionIds)) {
on<SelectOptionFilterListEvent>(
(event, emit) async {
await event.when(
initial: () async {
_startListening();
_loadOptions();
},
selectOption: (option) {
final selectedOptionIds = Set<String>.from(state.selectedOptionIds);
selectedOptionIds.add(option.id);
_updateSelectOptions(
selectedOptionIds: selectedOptionIds,
emit: emit,
);
},
unselectOption: (option) {
final selectedOptionIds = Set<String>.from(state.selectedOptionIds);
selectedOptionIds.remove(option.id);
_updateSelectOptions(
selectedOptionIds: selectedOptionIds,
emit: emit,
);
},
didReceiveOptions: (newOptions) {
List<SelectOptionPB> options = List.from(newOptions);
options.retainWhere(
(element) => element.name.contains(state.predicate));
final visibleOptions = options.map((option) {
return VisibleSelectOption(
option, state.selectedOptionIds.contains(option.id));
}).toList();
emit(state.copyWith(
options: options, visibleOptions: visibleOptions));
},
filterOption: (optionName) {
_updateSelectOptions(predicate: optionName, emit: emit);
},
);
},
);
}
@override
Future<void> close() async {
return super.close();
}
void _updateSelectOptions({
String? predicate,
Set<String>? selectedOptionIds,
required Emitter<SelectOptionFilterListState> emit,
}) {
final List<VisibleSelectOption> visibleOptions = _makeVisibleOptions(
predicate ?? state.predicate,
selectedOptionIds ?? state.selectedOptionIds,
);
emit(state.copyWith(
predicate: predicate ?? state.predicate,
visibleOptions: visibleOptions,
selectedOptionIds: selectedOptionIds ?? state.selectedOptionIds,
));
}
List<VisibleSelectOption> _makeVisibleOptions(
String predicate,
Set<String> selectedOptionIds,
) {
List<SelectOptionPB> options = List.from(state.options);
options.retainWhere((element) => element.name.contains(predicate));
return options.map((option) {
return VisibleSelectOption(option, selectedOptionIds.contains(option.id));
}).toList();
}
void _loadOptions() {
typeOptionContext.loadTypeOptionData(
onCompleted: (value) {
if (!isClosed) {
add(SelectOptionFilterListEvent.didReceiveOptions(value.options));
}
},
onError: (error) => Log.error(error),
);
}
void _startListening() {}
}
@freezed
class SelectOptionFilterListEvent with _$SelectOptionFilterListEvent {
const factory SelectOptionFilterListEvent.initial() = _Initial;
const factory SelectOptionFilterListEvent.selectOption(
SelectOptionPB option) = _SelectOption;
const factory SelectOptionFilterListEvent.unselectOption(
SelectOptionPB option) = _UnSelectOption;
const factory SelectOptionFilterListEvent.didReceiveOptions(
List<SelectOptionPB> options) = _DidReceiveOptions;
const factory SelectOptionFilterListEvent.filterOption(String optionName) =
_SelectOptionFilter;
}
@freezed
class SelectOptionFilterListState with _$SelectOptionFilterListState {
const factory SelectOptionFilterListState({
required List<SelectOptionPB> options,
required List<VisibleSelectOption> visibleOptions,
required Set<String> selectedOptionIds,
required String predicate,
}) = _SelectOptionFilterListState;
factory SelectOptionFilterListState.initial(List<String> selectedOptionIds) {
return SelectOptionFilterListState(
options: [],
predicate: '',
visibleOptions: [],
selectedOptionIds: selectedOptionIds.toSet(),
);
}
}
class VisibleSelectOption {
final SelectOptionPB optionPB;
final bool isSelected;
VisibleSelectOption(this.optionPB, this.isSelected);
}
@@ -0,0 +1,110 @@
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'filter_listener.dart';
import 'filter_service.dart';
part 'text_filter_editor_bloc.freezed.dart';
class TextFilterEditorBloc
extends Bloc<TextFilterEditorEvent, TextFilterEditorState> {
final FilterInfo filterInfo;
final FilterFFIService _ffiService;
final FilterListener _listener;
TextFilterEditorBloc({required this.filterInfo})
: _ffiService = FilterFFIService(viewId: filterInfo.viewId),
_listener = FilterListener(
viewId: filterInfo.viewId,
filterId: filterInfo.filter.id,
),
super(TextFilterEditorState.initial(filterInfo)) {
on<TextFilterEditorEvent>(
(event, emit) async {
event.when(
initial: () async {
_startListening();
},
updateCondition: (TextFilterCondition condition) {
_ffiService.insertTextFilter(
filterId: filterInfo.filter.id,
fieldId: filterInfo.field.id,
condition: condition,
content: state.filter.content,
);
},
updateContent: (content) {
_ffiService.insertTextFilter(
filterId: filterInfo.filter.id,
fieldId: filterInfo.field.id,
condition: state.filter.condition,
content: content,
);
},
delete: () {
_ffiService.deleteFilter(
fieldId: filterInfo.field.id,
filterId: filterInfo.filter.id,
fieldType: filterInfo.field.fieldType,
);
},
didReceiveFilter: (FilterPB filter) {
final filterInfo = state.filterInfo.copyWith(filter: filter);
final textFilter = filterInfo.textFilter()!;
emit(state.copyWith(
filterInfo: filterInfo,
filter: textFilter,
));
},
);
},
);
}
void _startListening() {
_listener.start(
onDeleted: () {
if (!isClosed) add(const TextFilterEditorEvent.delete());
},
onUpdated: (filter) {
if (!isClosed) add(TextFilterEditorEvent.didReceiveFilter(filter));
},
);
}
@override
Future<void> close() async {
await _listener.stop();
return super.close();
}
}
@freezed
class TextFilterEditorEvent with _$TextFilterEditorEvent {
const factory TextFilterEditorEvent.initial() = _Initial;
const factory TextFilterEditorEvent.didReceiveFilter(FilterPB filter) =
_DidReceiveFilter;
const factory TextFilterEditorEvent.updateCondition(
TextFilterCondition condition) = _UpdateCondition;
const factory TextFilterEditorEvent.updateContent(String content) =
_UpdateContent;
const factory TextFilterEditorEvent.delete() = _Delete;
}
@freezed
class TextFilterEditorState with _$TextFilterEditorState {
const factory TextFilterEditorState({
required FilterInfo filterInfo,
required TextFilterPB filter,
}) = _GridFilterState;
factory TextFilterEditorState.initial(FilterInfo filterInfo) {
return TextFilterEditorState(
filterInfo: filterInfo,
filter: filterInfo.textFilter()!,
);
}
}
@@ -16,25 +16,24 @@ import 'row/row_service.dart';
part 'grid_bloc.freezed.dart';
class GridBloc extends Bloc<GridEvent, GridState> {
final GridDataController dataController;
final GridController gridController;
void Function()? _createRowOperation;
GridBloc({required ViewPB view})
: dataController = GridDataController(view: view),
super(GridState.initial(view.id)) {
GridBloc({required ViewPB view, required this.gridController})
: super(GridState.initial(view.id)) {
on<GridEvent>(
(event, emit) async {
await event.when(
initial: () async {
_startListening();
await _loadGrid(emit);
await _openGrid(emit);
},
createRow: () {
state.loadingState.when(
loading: () {
_createRowOperation = () => dataController.createRow();
_createRowOperation = () => gridController.createRow();
},
finish: (_) => dataController.createRow(),
finish: (_) => gridController.createRow(),
);
},
deleteRow: (rowInfo) async {
@@ -66,17 +65,17 @@ class GridBloc extends Bloc<GridEvent, GridState> {
@override
Future<void> close() async {
await dataController.dispose();
await gridController.dispose();
return super.close();
}
GridRowCache? getRowCache(String blockId, String rowId) {
final GridBlockCache? blockCache = dataController.blocks[blockId];
final GridBlockCache? blockCache = gridController.blocks[blockId];
return blockCache?.rowCache;
}
void _startListening() {
dataController.addListener(
gridController.addListener(
onGridChanged: (grid) {
if (!isClosed) {
add(GridEvent.didReceiveGridUpdate(grid));
@@ -95,8 +94,8 @@ class GridBloc extends Bloc<GridEvent, GridState> {
);
}
Future<void> _loadGrid(Emitter<GridState> emit) async {
final result = await dataController.loadData();
Future<void> _openGrid(Emitter<GridState> emit) async {
final result = await gridController.openGrid();
result.fold(
(grid) {
if (_createRowOperation != null) {
@@ -124,7 +123,7 @@ class GridEvent with _$GridEvent {
RowsChangedReason listState,
) = _DidReceiveRowUpdate;
const factory GridEvent.didReceiveFieldUpdate(
UnmodifiableListView<GridFieldContext> fields,
List<FieldInfo> fields,
) = _DidReceiveFieldUpdate;
const factory GridEvent.didReceiveGridUpdate(
@@ -163,9 +162,9 @@ class GridLoadingState with _$GridLoadingState {
}
class GridFieldEquatable extends Equatable {
final UnmodifiableListView<GridFieldContext> _fields;
final List<FieldInfo> _fields;
const GridFieldEquatable(
UnmodifiableListView<GridFieldContext> fields,
List<FieldInfo> fields,
) : _fields = fields;
@override
@@ -182,6 +181,5 @@ class GridFieldEquatable extends Equatable {
];
}
UnmodifiableListView<GridFieldContext> get value =>
UnmodifiableListView(_fields);
UnmodifiableListView<FieldInfo> get value => UnmodifiableListView(_fields);
}
Loaded 100 of 679 files, more files were not shown because too many files have changed in this diff. Show more