Compare commits

...
140 Commits
Author SHA1 Message Date
Lucas.Xu 6d0c9f766b fix: filter chat page when duplicating (#5676)
* fix: filter chat page when duplicating

* fix: don't clear selection if it has been disposed
2024-07-02 14:35:29 +08:00
Nathan.fooo 7a3f013a87 chore: show ai service error (#5675) 2024-07-02 13:26:53 +08:00
Lucas.Xu 8c1520b273 feat: sync the created view after duplicating (#5674)
* feat: sync the created view after duplicating

* chore: revert launch.json

* chore: refacotor code
2024-07-02 13:02:15 +08:00
Mathias MogensenandLucas.Xu a7b850e752 chore: disable cloud search (#5663)
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-07-02 13:02:01 +08:00
Lucas.Xu 42dc6017c6 feat: support moving page to a space (#5665)
* feat: support moving page to a space

* chore: update collab repo
2024-07-02 10:29:46 +08:00
Stefan Weiberg f9fba6b561 chore: update German translations (#5640) 2024-07-02 09:44:16 +08:00
Nathan.fooo 46896b5a2b chore: remove appflowy_web project (#5671)
* chore: remove appflowy_web project

* chore: remove ci
2024-07-01 22:49:24 +08:00
Lucas.Xu 2b8dca209e feat: sync the documents and databases after batch importing documents and databases (#5644)
* feat: support batch import

* feat: support batch import database

* chore: revert launch.json

* chore: fix rust ci

* fix: rust ci
2024-07-01 14:44:08 +08:00
Lucas.Xu c78f23e1c0 fix: v0.6.2 issues (#5654)
* fix: remove create button in move to menu

* fix: add loading indicator when duplicating space

* fix: sidebar header expand icon status

* fix: text within select tag overflow

* fix: callout block icon align issue

* feat: sync the space icon when creating a space

* fix: duplicated hover views

* fix: cover image doesn't update
2024-07-01 14:43:57 +08:00
Lucas.Xu 50f5be3e75 chore: fix rustfmt ci test (#5664) 2024-07-01 13:26:43 +08:00
Nathan.fooo e1c68c1b72 feat: Run Local AI model in AppFlowy (#5655)
* chore: load plugin

* chore: sidecar

* chore: fix test

* chore: clippy

* chore: save chat config

* chore: arc plugin

* chore: add plugins

* chore: clippy

* chore: test streaming

* chore: config chat

* chore: stream message

* chore: response with local ai

* chore: fix compile

* chore: config ui

* chore: fix load plugin

* chore: add docs

* chore: update docs

* chore: disable local ai

* chore: fix compile

* chore: clippy
2024-06-30 17:38:39 +08:00
Mathias Mogensen 3bcadff152 fix: settings changes + ai layout improvements (#5656)
* fix: settings changes

* fix: improve chat layout
2024-06-28 22:54:54 +08:00
Mathias Mogensen b09ff040f1 fix: postpone fetching member (#5648) 2024-06-28 09:03:30 +08:00
Nathan.fooo bf087bba5d fix: score filter (#5645)
* fix: score filter

* chore: update score limit
2024-06-28 07:57:12 +08:00
Lucas.Xu 605a53f2ae feat: open the duplicated space by default (#5638) 2024-06-27 14:12:52 +08:00
Stefan Weiberg ecc5555a4e chore: update German translations (#5630) 2024-06-27 13:34:51 +08:00
Lucas.Xu d2a5bab6d9 chore: bump version 0.6.2 (#5637) 2024-06-27 12:02:15 +08:00
Lucas.Xu a1dec0f269 feat: add check workspace health button in settings page (#5631) 2024-06-27 09:57:06 +08:00
Mohammad Zolfaghari b24fbc6b60 fix: fill field_settings on creating linked view (#5598) 2024-06-27 03:07:08 +02:00
Mathias Mogensen bbb3f95a13 fix: billing+cleanup (#5634)
* fix: billing fixes

* chore: best practice and cleaning

* chore: dispose of unused resources

* chore: upgrade editor version
2024-06-27 02:00:21 +02:00
Mathias Mogensen 7859fc7922 fix: macos fullscreen window position (#5632) 2024-06-27 00:36:59 +02:00
Lucas.Xu f812040f04 feat: support creating database at the first level (#5627)
* feat: support creating database at the first level

* chore: add loading indicator when importing file
2024-06-26 13:27:33 +08:00
Lucas.Xu 7d96c2dfd4 feat: add key + as zoom-in key (#5628) 2024-06-26 13:27:13 +08:00
Mathias Mogensen e4132ed217 chore: enable cloud search (#5624)
* chore: enable cloud search

* fix: filter received msgs by query
2024-06-25 21:56:58 +02:00
Mathias Mogensen a7f40b2adc fix: paste in list (#5621)
* fix: support pasting in list

* test: add simple test

* chore: remove debugPrint
2024-06-25 20:08:01 +02:00
Mathias Mogensen c996c9c28e fix: maximized window optimization (#5622) 2024-06-25 20:07:43 +02:00
MarcinandLucas.Xu f59b8a2e5c chore: update Polish translations (#5593)
* chore: update translations with Fink 🐦

* Revert "chore: update translations with Fink 🐦"

This reverts commit e3b7ca9e22.

* chore: update pl-PL translations

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-06-25 21:16:35 +08:00
JakobandLucas.Xu f7480cba16 chore: update de-DE translations (#5588)
* chore: update translations with Fink 🐦

* Revert "chore: update translations with Fink 🐦"

This reverts commit f24a479b63.

* chore: update de-DE translations

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-06-25 21:09:02 +08:00
Mathias Mogensen 271b250eac fix: make theme uploads more tolerant (#5617) 2024-06-25 13:52:59 +02:00
Nathan.foooandLucas.Xu 6b1e7b6ac8 chore: set document id (#5585)
* chore: set document id

* feat: set document id to upload image api

* chore: fmt

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-06-25 17:36:41 +08:00
Lucas.Xu bf2a00b133 fix: prevent overfow of space name and view name (#5619)
* fix: space name overflow

* fix: space name overflow in space menu

* fix: view name overflow in favorite menu
2024-06-25 17:36:00 +08:00
Lucas.Xu a8ed93054c feat: support moving page across spaces (#5618)
* feat: support moving page across spaces

* feat: refacotor move api

* feat: filter the database views

* feat: support searching in move page menu
2024-06-25 16:43:58 +08:00
Lucas.Xu b9ad2768cf feat: duplicate space (#5612) 2024-06-25 10:03:02 +08:00
54c9d12171 feat: support switch model (#5575)
* feat: ai settings page

* chore: intergate client api

* chore: replace open ai calls

* chore: disable gen image from ai

* chore: clippy

* chore: remove learn about ai

* chore: fix wanrings

* chore: fix restart button title

* chore: remove await

* chore: remove loading indicator

---------

Co-authored-by: nathan <nathan@appflowy.io>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-06-25 07:59:38 +08:00
Lucas.Xu 40312f4260 fix: infinite loading (#5613) 2024-06-24 20:14:22 +08:00
Zack d2ca41c8f5 Remove deprecated add member (#5611)
* chore: remove deprecated add member directly

* chore: cargo clippy

* chore: cargo clippy

* chore: cargo clippy
2024-06-24 14:19:36 +08:00
Mathias Mogensen 7586a0ed48 fix: settings ui improvements (#5590)
* fix: settings ui improvements

* fix: replace placeholder + fix open folder

* fix: collapse sidebar button on windows

* test: fix tests after changes

* test: missed type cast
2024-06-20 21:01:20 +02:00
Mathias MogensenandLucas.Xu d8b32168a9 feat: hide application to dock macos (#5592)
* feat: hide application to tray macos

* chore: enable reopen the app after closing it

* fix: remove tray

* fix: remove asset

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-06-20 21:00:56 +02:00
Mathias Mogensen 8ca541bfa1 fix: reminder race condition (#5587) 2024-06-20 10:13:59 +02:00
Nathan.fooo b64da2c02f refactor: File upload (#5542)
* chore: rename service

* refactor: upload

* chore: save upload meta data

* chore: add sql test

* chore: uploader

* chore: fix upload

* chore: cache file and remove after finish

* chore: retry upload

* chore: pause when netowork unreachable

* chore: add event test

* chore: add test

* chore: clippy

* chore: update client-api commit id

* chore: fix flutter test
2024-06-20 07:44:57 +08:00
Lucas.Xu fdaca36b87 fix: automatically open page after switching space (#5580) 2024-06-19 21:28:49 +08:00
Lucas.Xu 8557383a89 fix: cursor height is inconsistent after upgrading to Flutter 3.22 (#5574)
* fix: cursor height is inconsistent after upgrading to Flutter 3.22

* fix: cursor height is inconsistent after upgrading to Flutter 3.22

* fix: android toolbar height issue
2024-06-19 19:34:47 +08:00
Mathias Mogensen fa86480458 feat: shortcuts page remake (#5567)
* feat: settings shortcuts page remake

* test: add shortcut test

* fix: physics on listview

* fix: menu icon
2024-06-19 10:24:34 +02:00
Nathan.fooo d75eb05707 chore: fix flutter test (#5572) 2024-06-19 13:26:18 +08:00
Lucas.Xu a2b32585a1 chore: bump version 0.6.0 (#5571) 2024-06-19 12:10:05 +08:00
Lucas.Xu 75cea400d2 fix: cursor jump randomly in check list item (#5565)
* chore: remove debug logs

* fix: cursor jump ramdomly in checklist item
2024-06-19 09:33:27 +08:00
Lucas.XuandMathias Mogensen ed82ec8eef chore: remove gl_es env (#5564)
* fix: try without media kit

* chore: remove gl_es env

* Revert "fix: try without media kit"

This reverts commit e331dbfabf.

---------

Co-authored-by: Mathias Mogensen <mathiasrieckm@gmail.com>
2024-06-18 16:16:31 +08:00
Lucas.XuandMathias Mogensen b6debd6c71 fix: remove media_kit (#5563)
Co-authored-by: Mathias Mogensen <mathiasrieckm@gmail.com>
2024-06-18 15:47:52 +08:00
Nathan.fooo a05ed63a3e chore: fix can't delete characters in card (#5562)
* chore: fix can't delete characters in card

* chore: i18n
2024-06-18 13:12:57 +08:00
Nathan.fooo 3e75f1f24a chore: fix checklist potential panic (#5561)
* chore: fix checklist

* chore: fix checklist
2024-06-18 10:16:39 +08:00
Lucas.Xu e607694729 chore: change slash menu hover color (#5555)
* feat: add a pinned icon after page name

* chore: change slack menu hover color

* Revert "feat: add a pinned icon after page name"

This reverts commit a668e0bd09.
2024-06-18 09:55:43 +08:00
ansizheshi 91d870415e chore: update zh-CN translations (#5545)
* chore: update translations with Fink 🐦

* Delete frontend/resources/translations/de-DE.json

* Add files via upload

* Restore en.json

* Restore en.json
2024-06-18 09:52:44 +08:00
Nathan.fooo 21a497924c chore: adjust height (#5558) 2024-06-17 21:38:05 +08:00
Mathias Mogensen 4a126e17ce fix: settings improvements (#5547)
* fix: user workspace bloc changed

* fix: use translate card cell style

* fix: add getworkspacemember

* fix: billing launch review

* fix: disable time field

* fix: member tooltip

* fix: remove my account description

* fix: punctuation

* fix: filter workspace font

* fix: cloud toggle

* fix: minor adjustments

* chore: disable cloud document search

* fix: improve workspace name textfield

* test: move billing test to cloud

* fix: use cache over remote

* fix: clippy and tests

* chore: amend flowy tooltip

* test: add pump and settle

* test: integration test for local auth
2024-06-17 14:30:19 +02:00
Lucas.Xu 8bf97ad5c6 feat: add a pinned icon after page name (#5553) 2024-06-17 19:18:06 +08:00
Lucas.Xu a85d11fb6d feat: collapse space and switch space by shortcuts (#5554)
* chore: improve delete popup style

* feat: support collapsing space and switching space by shortcuts
2024-06-17 18:59:08 +08:00
Lucas.Xu 1fd34bc58a chore: turn off the billing feature (#5551) 2024-06-17 17:33:40 +08:00
Lucas.Xu 70a7b856a2 feat: improve space UX (#5549)
* fix: scroll bar hover color

* chore: replace board icon

* chore: remove close button in upgrade menu

* chore: add translation

* feat: switch to the next space by shortcut

* chore: replace space lock icon

* chore: remove arrow icon in change icon button

* feat: only show other favorites header if the other two is empty

* fix: create new page in current space

* feat: create a new page in a newly created space by default

* chore: update translations

* feat: open the first page after switching space by default

* chore: replace board icon

* fix: open the first page(non-space) after switching workspace

* chore: revert more icon

* chore: revert editor version
2024-06-17 16:23:17 +08:00
Lucas.Xu 49683e6079 fix: migrate the private views only if they are not empty (#5544) 2024-06-17 10:29:15 +08:00
Lucas.Xu 13107b63e9 feat: improve upgrade space toast (#5543)
* fix: divider color

* feat: improve upgrade space toast
2024-06-15 09:03:09 +08:00
Nathan.fooo ce1a6e4fca chore: highlight code block (#5540) 2024-06-14 14:53:53 +08:00
Lucas.Xu 1d23e28eaf chore: enable billing system (#5539) 2024-06-14 08:42:20 +02:00
Lucas.Xu 2a4d78b688 feat: improve hover color in dark mode (#5538) 2024-06-14 14:31:55 +08:00
Lucas.Xu cbe452a73d feat: improve page thumbnail on mobile (#5535)
* feat: improve view thumbnail

* chore: blur the bottom navigation bar

* feat: improve date format

* feat: support url avatar

* fix: remove duplicated divider
2024-06-14 13:15:41 +08:00
Nathan.fooo 27899ee993 chore: save workspace member info to disk (#5536)
* chore: save workspace member info to disk

* chore: fix clippy
2024-06-14 11:34:23 +08:00
Lucas.Xu 785597f53e fix: space issues (#5531)
* fix: display the space icon in breadcrumb and disable the space entry

* fix: the icon and the title are not aligned center in more menu

* fix: incorrect space icon corner radius

* fix: missed tooltip for add new page button

* fix: disable space migration for local user

* chore: use general as default space name

* fix: space name overflow

* fix: only show arrow button when hovering on page

* fix: sidebar tooltip font size

* fix: use space name as hint text

* feat: support adding a new space from space menu

* fix: filter the space view
2024-06-14 09:32:02 +08:00
Nathan.fooo e2ce274718 chore: polish chat UI (#5533)
* chore: adjust chat ui

* chore: clippy
2024-06-14 09:02:06 +08:00
Zack 6be9c00127 chore: update to latest client api (#5529) 2024-06-13 15:14:20 +08:00
Mohammad ZolfaghariandMathias Mogensen aa621a8d84 feat: timer field (#5349)
* feat: wip timer field

* feat: timer field fixing errors

* feat: wip timer field frontend

* fix: parsing TimerCellDataPB

* feat: parse time string to minutes

* fix: don't allow none number input

* fix: timer filter

* style: cargo fmt

* fix: clippy errors

* refactor: rename field type timer to time

* refactor: missed some variable and files to rename

* style: cargo fmt fix

* feat: format time field type data in frontend

* style: fix cargo fmt

* fix: fixes after merge

---------

Co-authored-by: Mathias Mogensen <mathiasrieckm@gmail.com>
2024-06-13 08:52:13 +02:00
Mathias Mogensen 2d4300e931 chore: update version to 0.6.0 (#5528) 2024-06-13 08:25:11 +02:00
Lucas.Xu dc12938ab6 feat: support switching space on mobile (#5527) 2024-06-13 14:14:18 +08:00
Lucas.Xu 2d674060c6 feat: introduce space to manage the pages (#5517)
* fix: resizing icon on mobile

* feat: add space feature

* feat: support creating space

* feat: support creating new space

* feat: support space expand status

* feat: support creating page in space

* feat: support customizing space icon

* feat: display the space icon on space menu

* feat: add space more action button

* fix: flutter analyze

* feat: support editing space icon on more menu

* chore: update translations

* feat: manage space

* feat: delete workspace

* feat: disable delete button if needed

* feat: add private lock

* chore: adjust the old version

* feat: display upgrade button

* feat: support migrating space

* feat: support migrating space

* feat: allow user to upgrade space maunally

* fix: dark mode issue

* fix: create space delay

* chore: translations

* chore: disable workspace test
2024-06-13 13:43:29 +08:00
Nathan.fooo ecca81f3b8 chore: exclude ai columns (#5526) 2024-06-13 11:15:59 +08:00
Lucas.Xu 15329b39d0 chore: update editor version (#5513) 2024-06-13 07:58:05 +08:00
Mathias Mogensen bd5f5f8b9e feat: integrate cloud document search (#5523) 2024-06-13 01:37:19 +02:00
4f4be7eac7 feat: enable group condition (#5248)
* feat: enable group condition

* style: added i18n for date field group conditions

* fix: flutter analyze

* fix: test use i18n

* fix: more localization

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
Co-authored-by: Mathias Mogensen <mathias@appflowy.io>
2024-06-13 00:02:16 +02:00
Nathan.fooo f1a4deb459 chore: support more language (#5522)
* chore: support more language

* chore: adjust ui
2024-06-12 23:17:57 +08:00
Mathias MogensenandZack Fu Zi Xiang 4708c0f779 feat: plan+billing (#5518)
* feat: billing client

* feat: subscribe workspace default impl

* feat: added create subscription

* feat: add get workspace subs

* feat: added subscription cancellation

* feat: add workspace limits api

* fix: update client api

* feat: user billing portal

* feat: billing UI (#5455)

* feat: plan ui

* feat: billing ui

* feat: settings plan comparison dialog

* feat: complete plan+billing ui

* feat: backend integration

* chore: cleaning

* chore: fixes after merge

* fix: dependency issue

* feat: added subscription plan cancellation information

* feat: subscription callback + canceled date

* feat: put behind feature flag

* feat: downgrade/upgrade dialogs

* feat: update limit error codes

* fix: billing refresh + downgrade dialog

* fix: some minor improvements to settings

* chore: use patch for client-api in tauri

* fix: add shared-entity to patch

* fix: compile

* ci: try to add back maximize build space step

* test: increase timeout in failing test

---------

Co-authored-by: Zack Fu Zi Xiang <speed2exe@live.com.sg>
2024-06-12 17:08:55 +02:00
Nathan.fooo 3d7a500550 feat: AI translation in Database (#5515)
* chore: add tranlate field type

* chore: integrate ai translate

* chore: integrate client api

* chore: implement UI
2024-06-12 16:32:28 +08:00
Mathias Mogensen 815c99710e feat: magic link sign-in as anonymous user (#5520)
* feat: magic link sign-in as anonymous user

* chore: remove commented code

* fix: custom sign-in dialog

* fix: bring back sign-out dialog

* fix: add minor space for sync indicator
2024-06-11 22:17:29 +02:00
Murat Mirgün ERCAN c9fe93920f chore: update tr_TR translations (#5499) 2024-06-11 10:17:28 +08:00
Lucas.Xu 5012b99c9b fix: remove member request on launch (#5505) 2024-06-11 10:16:32 +08:00
Mathias Mogensen 6b46372aa0 fix: custom font dropdown (#5502)
* fix: custom font dropdown

* fix: dart analyzer
2024-06-10 15:23:26 +02:00
Mathias Mogensen b6d873db1b chore: remove build bot (#5500) 2024-06-10 09:04:28 +08:00
Lucas.Xu 38d2bd7ee8 fix: board shortcuts conflict with textfield (#5487)
* fix: board shortcuts conflict with text field

* fix: typo

* fix: favorite icon issue in dark mode
2024-06-10 09:04:01 +08:00
Nathan.fooo bb3e9d5bd8 feat: Stream chat message (#5498)
* chore: stream message

* chore: stream message

* chore: fix streaming

* chore: fix clippy
2024-06-09 14:02:32 +08:00
Kilu.He 94060a0a99 fix: generate default document data from document id (#5430)
* fix: generate default document data from document id

* fix: update version
2024-06-09 13:42:09 +08:00
Kilu.He 86696b271e chore: improvements to caching strategies (#5488) 2024-06-07 16:37:07 +08:00
Tianyu Yuan 76fde00cc4 chore: update zh-CN translations (#5484)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2024-06-07 11:48:23 +08:00
Kilu.He b794f3894e chore: improve coverage of cypress (#5483) 2024-06-06 19:47:14 +08:00
Kilu.He 3b72f90ca5 feat: improve test coverage (#5481) 2024-06-06 17:48:58 +08:00
Lucas.Xu d73e388d01 chore: disable sync indicator (#5479) 2024-06-05 21:36:44 +08:00
Stefan Weiberg 383e06ff2d chore: update German translations (#5456)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2024-06-05 19:57:31 +08:00
Mathias MogensenandAPPFLOWY\tsuiy ff93fbf0e6 fix: use bitsdojo_window on windows (#5477)
Co-authored-by: APPFLOWY\tsuiy <lucas.xu@appflowy.io>
2024-06-05 19:56:22 +08:00
Mathias Mogensen bd7977d8ba fix: search improvements (#5473)
* fix: search workspace sync indexing

* chore: update collab rev temporarily

* feat: revert comparison and implement index check

* chore: fixes after merg

* feat: clean code + support delete workspace

* fix: improve code

* fix: improvements after merge

* fix: cargo fmt

* fix: remove indices for workspace method

* fix: clippy errors

* fix: clippy too many arguments
2024-06-05 13:44:32 +02:00
Lucas.Xu 6e7d044208 chore: update chat icon (#5476)
* chore: update chat icon

* chore: update translation
2024-06-05 17:32:30 +08:00
Lucas.Xu d8ca693976 feat: enable search (#5474) 2024-06-05 16:01:42 +08:00
Kilu.He 52e7fb49cd feat: support server render html (#5471)
* feat: node server

* fix: replace node to bun
2024-06-05 14:08:19 +08:00
Nathan.fooo f6e33a59d5 chore: fix document test (#5470)
* chore: fix document test
2024-06-05 12:40:31 +08:00
nathan a0e981ec6b chore: downgrade diesel version in appflowy web 2024-06-05 11:21:24 +08:00
Mathias Mogensenandnathan 041e3c155a fix: search workspace sync indexing (#5437)
* fix: search workspace sync indexing

* chore: update collab rev temporarily

* feat: revert comparison and implement index check

* chore: fixes after merg

* chore: enable search

* chore: disable ai test

---------

Co-authored-by: nathan <nathan@appflowy.io>
2024-06-05 10:05:51 +08:00
Lucas.Xu e4eff7e632 feat: support slide actions on mobile (#5463)
* feat: support slide actions on mobile

* fix: some ui issues

* fix: scale down emoji size on windows

* fix: flutter analyze

* fix: force text height on macos
2024-06-05 09:18:43 +08:00
Nathan.fooo 9f66dcdc8f chore: bump client api (#5467) 2024-06-04 22:43:38 +08:00
Nathan.fooo 890ac5a213 chore: chat icon (#5465) 2024-06-04 20:13:54 +08:00
Nathan.fooo 1cecfae6f4 chore: fix ui color issue after merge main (#5464) 2024-06-04 17:12:49 +08:00
Lucas.Xu 1757570337 feat: change user avatar corner radius (#5461) 2024-06-04 15:59:56 +08:00
Lucas.Xu 57d4652824 fix: some keys conflict with board shortcuts (#5460) 2024-06-04 11:37:06 +08:00
Lucas.Xu 1e485188eb fix: font not found error log (#5449)
* fix: font not found error log

* fix: flutter analyze
2024-06-04 09:28:28 +08:00
Lucas.Xu 03e8dba5f3 fix: improve sidebar UI (#5453)
* fix: revert dark main color

* fix: workspace name font size

* fix: only show collapse button when hovering the sidebar

* fix: remove expand button after workspace name

* fix: change settings icon opactiy to 0.7

* chore: replace favorite icon

* chore: add arrow icon after section name

* chore: replace trash bin icon

* chore: adjust collapse icon padding

* feat: redesign question bubble

* fix: replace favorite icon

* fix: remove trash button padding

* chore: replace hover color

* feat: add more padding to tooltip

* feat: add hover effect to arrow icon

* feat: add hover effect to add view button

* fix: view title color

* feat: replace question bubble

* feat: replace default page icon

* fix: header icon size

* fix: add view button padding

* fix: unable to insert default image on desktop

* fix: notification button size issue

* fix: workspace icon size

* feat: add a divider between favorite space and add new page button
2024-06-04 09:22:47 +08:00
Nathan.fooo d5cfd054cc chore: add default questions (#5457)
* chore: add default questions

* chore: fix mobile ui

* chore: send button

* chore: send button
2024-06-03 23:20:33 +08:00
Nathan.fooo 9c9168ac5b fix: workspace id not match (#5454) 2024-06-03 16:34:54 +08:00
nathan 4762d69851 chore: adjust chat ui 2024-06-03 14:34:48 +08:00
Nathan.fooo aec7bc847e feat: AI chat (#5383)
* chore: ai type

* chore: use patch to fix version issue

* chore: update

* chore: update

* chore: integrate client api

* chore: add schema

* chore: setup event

* chore: add event test

* chore: add test

* chore: update test

* chore: load chat message

* chore: load chat message

* chore: chat ui

* chore: disable create chat

* chore: update client api

* chore: disable chat

* chore: ui theme

* chore: ui theme

* chore: copy message

* chore: fix test

* chore: show error

* chore: update bloc

* chore: update test

* chore: lint

* chore: icon

* chore: hover

* chore: show unsupported page

* chore: adjust mobile ui

* chore: adjust view title bar

* chore: return related question

* chore: error page

* chore: error page

* chore: code format

* chore: prompt

* chore: fix test

* chore: ui adjust

* chore: disable create chat

* chore: add loading page

* chore: fix test

* chore: disable chat action

* chore: add maximum text limit
2024-06-03 14:27:28 +08:00
Kilu.He 4d42c9ea68 feat: support document apply remote events (#5436)
* feat: support document apply remote events

* fix: add tests for database

* fix: add test for filter,sort and group

* fix: jest ci

* fix: jest ci

* fix: jest ci

* fix: jest ci

* fix: cypress test
2024-06-03 11:20:45 +08:00
Lucas.Xu 0d64aa4311 feat: integrate add new page button on mobile (#5450)
* chore: replace home icon

* feat: implement add new page button

* chore: refactor navigation bottom items
2024-06-03 09:11:03 +08:00
Lucas.Xu 2a59579c17 chore: call FolderEventSetLatestView when opening page on mobile (#5447)
* fix: call FolderEventSetLatestView when opening page on mobile

* chore: remove unused log
2024-06-03 09:10:02 +08:00
Mathias Mogensen 4ad7c48b25 fix: launch review 0.5.9 (#5443)
* fix: lose focus in editor on open settings dialog

* fix: support CTRL+. for sidebar toggle

* fix: make notify method private

* fix: copy for video block

* fix: copy for notification setting

* fix: add libmpv to appimage builder

* fix: missing tabs bloc from context

* ci: add libmpv-dev to missing workflows

* fix: do not depend on inherited widget in dispose

* test: add media kit ensureInitialized to integration tests

* fix: use maybeOf for AFFocusManager

* fix: use pattern matching for youtube error

* fix: missed null-promise on convertion
2024-06-02 14:52:20 +02:00
Lucas.XuandMathias Mogensen 2c0cdfa6c5 fix: sidebar issues (#5444)
* fix: cannot open the view in favorite menu

* fix: pinned docs should also show up in the search list

* fix: the search result doesn't update after deleting query

* fix: remove widget button

* fix: translation

* fix: replace icon selector

* fix: translations

* fix: recent page title sycn slowly

* fix: unable to favorite database on mobile

* fix: auto focus on search textfield when opening favorite menu

* feat: extend the expand icon hit test area

* fix: mobile tab bar dark mode issue

* fix: keep tab views alive

* chore: update frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
2024-05-31 10:34:23 +08:00
Lucas.Xu 8b04506ac4 fix: notification test (#5440)
* fix: notification test

* fix: use default emoji font family on iOS

* fix: unable to delete duplicated recent view
2024-05-30 15:19:07 +08:00
Lucas.Xu a1395cb54a fix: some list icons don't align with the paragraph (#5439) 2024-05-30 11:58:36 +08:00
migcarde 526de2eb5c feat: integrate show notification button option (#5302) 2024-05-30 11:48:23 +08:00
AlainandLucas.Xu e40e1e9a8a feat: add border to selected unsplash image (#5428)
* feat: add border to selected unsplash image

* feat: add border to selected unsplash image

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-05-30 10:27:13 +08:00
Lucas.Xu ace729eb78 feat: sidebar UI Revamp on mobile (#5418)
* chore: replace settings icon and expand icon

* feat: use tabbar view to control the spaces

* feat: improve space UI design on mobile

* feat: improve recent space UI design on mobile

* feat: improve recent space UI design on mobile

* feat: improve favorite space UI design on mobile

* feat: improve header UI design on mobile

* feat: expand header height

* feat: update BottomNavigationBarItem icon

* fix: recent views and favorite views doesn't reload after switching workspace

* feat: improve recent view UI design on mobile

* feat: improve recent/favorite view UI design on mobile

* feat: add empty placeholder for recent/favorite space

* feat: long press on recent card to show bottom sheet

* feat: support removing page from recent

* feat: add trash button

* chore: remove recent top padding

* feat: support user avatar

* feat: support ... and + in space page

* chore: disable user avatar

* feat: optimize title display on mobile

* feat: support ... menu on Space page

* chore: add tab padding

* chore: cache image

* chore: optimize the mobile card view height

* feat: reoder tab on mobile

* fix: some emojis may not display correctly on Android devices

* fix: ignore the last edit time on test
2024-05-30 09:56:44 +08:00
Stefan Weiberg 189f0e4b58 chore: update German translations with Fink 🐦 (#5421) 2024-05-30 09:41:59 +08:00
Abhijeet SinghandLucas.Xu e8afcbb828 chore: update Spanish translations (#5205)
* Fink 🐦: update translations

* chore: update translations with Fink 🐦

* chore: revert translations

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-05-30 09:41:16 +08:00
Mathias Mogensen 42e83b3ce9 feat: video block support (#5199)
* feat: video block support

* chore: workaround for ci failing

* chore: test

* chore: check status

* chore: revert apt-get

* chore: add mpv

* chore: add mpv to appimagebuilder

* chore: try again

* chore: update after merge

* ci: remove workaround for microsoft issue

* chore: update editor plugins

* feat: add video block option on mobile

* fix: final changes for menu

* chore: undo cocoapods version
2024-05-30 03:06:29 +02:00
Sander HoeveandMathias Mogensen 859eaf903b feat: add reset app scale hotkey (#5386)
Co-authored-by: Mathias Mogensen <mathiasrieckm@gmail.com>
2024-05-30 02:30:40 +02:00
Lucas.Xu 0bfe071caf fix: sidebar issues on Windows and Linux (#5431)
* fix: workspace icon overflow

* fix: notification button doesn't have same effect as settings button

* fix: open-sidebar button wasn't replaced in the Windows title bar

* fix: workspace name overflow on Linux

* fix: appflowy logo align issue
2024-05-29 22:05:20 +08:00
Kilu.He b8b7a10b33 feat: support web layout setting and breadcrumbs (#5425)
* fix: some bugs

* fix: performance

* feat: support system dark mode and different language

* feat: support breadcrumb

* feat: support breadcrumb

* feat: support new doucment title

* feat: support new doucment title
2024-05-29 10:17:50 +08:00
Lucas.Xu cb44a885a1 fix: dragging the Unsplash cover triggers an assertion error (#5404) 2024-05-27 15:37:05 +08:00
Lucas.Xu 083be32fa6 chore: improve hover and text colors in dark mode (#5416) 2024-05-27 15:36:45 +08:00
Mayur Mahajan 8b6575d1ee fix: accept multi-key combination for customizing shortcuts & removes duplicates (#5414) 2024-05-27 08:57:51 +08:00
Lucas.Xu a8f136eda2 feat: sidebar UI Revamp on Desktop (#5343) 2024-05-27 08:51:49 +08:00
Mohammad Zolfaghari 13b3439bd6 fix: default text direction not synced (#5405)
* fix: default text direction not synced

* test: annotate visibleForTesting
2024-05-24 15:17:35 +02:00
Stefan Weiberg 13a35672ab chore: update German translations (#5382)
* chore: update translations with Fink 🐦

* Manually editing German translation

Had to edit the German translation manually, as inlang didn't want to change the strings marked for translation.

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2024-05-24 13:24:41 +08:00
Kilu.He 857b3aa106 feat: support preview grid/board/calendar block on web (#5401) 2024-05-23 21:16:52 +08:00
Kilu.He a0139dd475 feat: support open row page (#5400) 2024-05-23 16:35:45 +08:00
Lucas.Xu 9a5dbbb3ce chore: upgrade to Flutter 3.22.0 (#5395)
* chore: upgrade flutter to 3.22.0

* chore: upgrade editor version

* chore: upgrade CI files

* fix: failed tests
2024-05-23 11:07:09 +08:00
Kilu.He acae34836e feat: support preview the calendar view on web (#5394) 2024-05-22 21:00:56 +08:00
Nathan.fooo 68c4e19f91 chore: set min version number (#5390)
* chore: set min version number

* chore: fix test
2024-05-22 14:24:11 +08:00
1261 changed files with 49671 additions and 21191 deletions

No files matched your search

+1 -1
View File
@@ -63,7 +63,7 @@ runs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
elif [ "$RUNNER_OS" == "Windows" ]; then
vcpkg integrate install
elif [ "$RUNNER_OS" == "macOS" ]; then
@@ -52,7 +52,7 @@ runs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager libmpv-dev mpv
shell: bash
- name: Enable Flutter Desktop
+1 -1
View File
@@ -19,7 +19,7 @@
# env:
# CARGO_TERM_COLOR: always
# FLUTTER_VERSION: "3.19.0"
# FLUTTER_VERSION: "3.22.0"
# RUST_TOOLCHAIN: "1.77.2"
# CARGO_MAKE_VERSION: "0.36.6"
-28
View File
@@ -1,28 +0,0 @@
name: Build Bot
on:
issue_comment:
types: [created]
jobs:
dispatch_slash_command:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v4
# get build name from pubspec.yaml
- name: Get build version
working-directory: frontend/appflowy_flutter
id: get_build_name
run: |
echo "fetching version from pubspec.yaml..."
echo "build_name=$(grep 'version: ' pubspec.yaml | awk '{print $2}')" >> $GITHUB_OUTPUT
- uses: peter-evans/slash-command-dispatch@v4
with:
token: ${{ secrets.PAT }}
commands: build
static-args: |
ref=refs/pull/${{ github.event.issue.number }}/head
build_name=${{ steps.get_build_name.outputs.build_name }}
+1 -1
View File
@@ -62,7 +62,7 @@ jobs:
with:
SSH_PRIVATE_KEY: ${{ env.SSH_PRIVATE_KEY }}
ARGS: "-rlgoDzvc -i"
SOURCE: "frontend/appflowy_web_app/dist frontend/appflowy_web_app/Dockerfile frontend/appflowy_web_app/nginx.conf frontend/appflowy_web_app/.env nginx-signed.crt nginx-signed.key"
SOURCE: "frontend/appflowy_web_app/dist frontend/appflowy_web_app/server.cjs frontend/appflowy_web_app/start.sh frontend/appflowy_web_app/Dockerfile frontend/appflowy_web_app/nginx.conf frontend/appflowy_web_app/.env nginx-signed.crt nginx-signed.key"
REMOTE_HOST: ${{ env.REMOTE_HOST }}
REMOTE_USER: ${{ env.REMOTE_USER }}
EXCLUDE: "frontend/appflowy_web_app/dist/, frontend/appflowy_web_app/node_modules/"
+23 -3
View File
@@ -25,7 +25,7 @@ on:
env:
CARGO_TERM_COLOR: always
FLUTTER_VERSION: "3.19.0"
FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.77.2"
CARGO_MAKE_VERSION: "0.36.6"
@@ -89,6 +89,7 @@ jobs:
with:
os: ${{ matrix.os }}
flutter_version: ${{ env.FLUTTER_VERSION }}
DISABLE_CI_TEST_LOG: "true"
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
rust_target: ${{ matrix.target }}
@@ -172,7 +173,7 @@ jobs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
fi
shell: bash
@@ -202,6 +203,7 @@ jobs:
- name: Run Flutter unit tests
env:
DISABLE_EVENT_LOG: true
DISABLE_CI_TEST_LOG: "true"
working-directory: frontend
run: |
if [ "$RUNNER_OS" == "macOS" ]; then
@@ -272,7 +274,7 @@ jobs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
shell: bash
- name: Enable Flutter Desktop
@@ -319,6 +321,12 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Install video dependency
run: |
sudo apt-get update
sudo apt-get -y install libmpv-dev mpv
shell: bash
- name: Flutter Integration Test 1
uses: ./.github/actions/flutter_integration_test
with:
@@ -343,6 +351,12 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Install video dependency
run: |
sudo apt-get update
sudo apt-get -y install libmpv-dev mpv
shell: bash
- name: Flutter Integration Test 2
uses: ./.github/actions/flutter_integration_test
with:
@@ -367,6 +381,12 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Install video dependency
run: |
sudo apt-get update
sudo apt-get -y install libmpv-dev mpv
shell: bash
- name: Flutter Integration Test 3
uses: ./.github/actions/flutter_integration_test
with:
+1 -1
View File
@@ -20,7 +20,7 @@ on:
- "!frontend/appflowy_web_app/**"
env:
FLUTTER_VERSION: "3.19.0"
FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.77.2"
concurrency:
+3 -3
View File
@@ -6,7 +6,7 @@ on:
- "*"
env:
FLUTTER_VERSION: "3.19.0"
FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.77.2"
jobs:
@@ -367,8 +367,8 @@ jobs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo apt-get update
sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
sudo apt-get install keybinder-3.0 libnotify-dev
sudo apt-get -y install alien
sudo apt-get install keybinder-3.0
sudo apt-get install -y alien libnotify-dev libmpv-dev mpv
source $HOME/.cargo/env
cargo install --force cargo-make
cargo install --force duckscript_cli
+8 -8
View File
@@ -32,14 +32,14 @@ jobs:
# swap-size-mb: 1024
# remove-dotnet: 'true'
# # the following step is required to avoid running out of space
# - name: Maximize build space
# run: |
# sudo rm -rf /usr/share/dotnet
# sudo rm -rf /opt/ghc
# sudo rm -rf "/usr/local/share/boost"
# sudo rm -rf "$AGENT_TOOLSDIRECTORY"
# sudo docker image prune --all --force
# the following step is required to avoid running out of space
- name: Maximize build space
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo docker image prune --all --force
- name: Checkout source code
uses: actions/checkout@v4
+1 -1
View File
@@ -10,7 +10,7 @@ on:
env:
CARGO_TERM_COLOR: always
FLUTTER_VERSION: "3.19.0"
FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.77.2"
jobs:
+2 -2
View File
@@ -52,11 +52,11 @@ jobs:
working-directory: frontend/appflowy_web_app
run: |
pnpm install
- name: test and lint
- name: Run lint check
working-directory: frontend/appflowy_web_app
run: |
pnpm run lint
pnpm run test:unit
- name: build and analyze
working-directory: frontend/appflowy_web_app
run: |
-83
View File
@@ -1,83 +0,0 @@
name: WEB-CI
on:
workflow_dispatch:
inputs:
build:
description: 'Build the web app'
required: true
default: 'true'
env:
CARGO_TERM_COLOR: always
NODE_VERSION: "18.16.0"
PNPM_VERSION: "8.5.0"
RUST_TOOLCHAIN: "1.77.2"
CARGO_MAKE_VERSION: "0.36.6"
jobs:
web-build:
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
platform: [ ubuntu-latest ]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: setup node
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache Rust Dependencies
uses: Swatinem/rust-cache@v2
with:
key: rust-dependencies-${{ runner.os }}
workspaces: |
frontend/rust-lib
frontend/appflowy_web/appflowy_wasm
# TODO: Can combine caching deps and node_modules in one
# See Glob patterns: https://github.com/actions/toolkit/tree/main/packages/glob
- name: Cache Node.js dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}
- name: Cache node_modules
uses: actions/cache@v4
with:
path: frontend/appflowy_web/node_modules
key: node-modules-${{ runner.os }}
- name: Install Rust toolchain
id: rust_toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
profile: minimal
- name: Install wasm-pack
run: cargo install wasm-pack
- uses: taiki-e/install-action@v2
with:
tool: cargo-make@${{ env.CARGO_MAKE_VERSION }}
- name: install dependencies
if: matrix.platform == 'ubuntu-latest'
working-directory: frontend
run: |
sudo apt-get update
npm install -g pnpm@${{ env.PNPM_VERSION }}
cargo make install_web_protobuf
- name: Build
working-directory: frontend/appflowy_web
run: |
pnpm install
pnpm run build_release_wasm
@@ -1,4 +1,4 @@
name: Cypress Tests
name: Web Code Coverage
on:
pull_request:
@@ -6,6 +6,7 @@ on:
- ".github/workflows/web2_ci.yaml"
- "frontend/appflowy_web_app/**"
- "frontend/resources/**"
env:
NODE_VERSION: "18.16.0"
PNPM_VERSION: "8.5.0"
@@ -13,7 +14,7 @@ concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
cypress-run:
test:
if: github.event.pull_request.draft != true
runs-on: ubuntu-22.04
steps:
@@ -45,4 +46,20 @@ jobs:
component: true
build: pnpm run build
start: pnpm run start
browser: chrome
browser: chrome
- name: Jest run
working-directory: frontend/appflowy_web_app
run: |
pnpm run test:unit
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
with:
token: cf9245e0-e136-4e21-b0ee-35755fa0c493
files: frontend/appflowy_web_app/coverage/jest/lcov.info,frontend/appflowy_web_app/coverage/cypress/lcov.info
flags: appflowy_web_app
name: frontend/appflowy_web_app
fail_ci_if_error: true
verbose: true
+34 -1
View File
@@ -1,4 +1,37 @@
# Release Notes
## Version 0.6.2 - 01/07/2024
### New Features
- Added support for duplicating spaces.
- Added support for moving pages across spaces.
- Undo markdown formatting with `Ctrl + Z` or `Cmd + Z`.
- Improved shortcuts settings UI.
### Bug Fixes
- Fixed unable to zoom in with `Ctrl` and `+` or `Cmd` and `+` on some keyboards.
- Fixed unable to paste nested lists in existing lists.
## Version 0.6.1 - 22/06/2024
### New Features
- Introduced the "Space" feature to help you organize your pages more efficiently.
### Bug Fixes
- Resolved shortcut conflicts on the board page.
- Resolved an issue where underscores could cause the editor to freeze.
## Version 0.6.0 - 19/06/2024
### New Features
- Introduced the "Space" feature to help you organize your pages more efficiently.
### Bug Fixes
- Resolved shortcut conflicts on the board page.
- Resolved an issue where underscores could cause the editor to freeze.
## Version 0.5.9 - 06/06/2024
### New Features
- Revamped the sidebar for both Desktop and Mobile.
- Added support for embedding videos in documents.
- Introduced a hotkey (Cmd/Ctrl + 0) to reset the app scale.
- Supported searching the workspace by page title.
### Bug Fixes
- Fixed the issue preventing the use of Backspace to delete words in Kanban boards.
## Version 0.5.8 - 05/20/2024
### New Features
- Improvement to the Callout block to insert new lines
@@ -105,7 +138,7 @@
- Fixed a bug where newly created rows were not being automatically sorted.
- Fixed issues related to deleting a sorting field or sort not removing existing sorts properly.
### Notes
- Windows 7, Windows 8, and iOS 11 are not yet supported due to the upgrade to Flutter 3.19.0.
- Windows 7, Windows 8, and iOS 11 are not yet supported due to the upgrade to Flutter 3.22.0.
## Version 0.4.9 - 02/17/2024
### Bug Fixes
+1 -2
View File
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
APPFLOWY_VERSION = "0.5.8"
APPFLOWY_VERSION = "0.6.2"
FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"
@@ -50,7 +50,6 @@ APP_ENVIRONMENT = "local"
FLUTTER_FLOWY_SDK_PATH = "appflowy_flutter/packages/appflowy_backend"
TAURI_BACKEND_SERVICE_PATH = "appflowy_tauri/src/services/backend"
WEB_BACKEND_SERVICE_PATH = "appflowy_web/src/services/backend"
WEB_LIB_PATH = "appflowy_web/wasm-libs/af-wasm"
TAURI_APP_BACKEND_SERVICE_PATH = "appflowy_web_app/src/application/services/tauri-services/backend"
# Test default config
TEST_CRATE_TYPE = "cdylib"
@@ -52,7 +52,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "io.appflowy.appflowy"
minSdkVersion 23
minSdkVersion 24
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@@ -58,4 +58,11 @@
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
<!--
Media access permissions.
Android 13 or higher.
Used for VideoBlock (edia_kit)
-->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
</manifest>
@@ -1,7 +1,6 @@
import 'anon_user_continue_test.dart' as anon_user_continue_test;
import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
import 'empty_test.dart' as preset_af_cloud_env_test;
// import 'document_sync_test.dart' as document_sync_test;
import 'user_setting_sync_test.dart' as user_sync_test;
import 'workspace/change_name_and_icon_test.dart'
as change_workspace_name_and_icon_test;
@@ -10,13 +9,8 @@ import 'workspace/collaborative_workspace_test.dart'
Future<void> main() async {
preset_af_cloud_env_test.main();
appflowy_cloud_auth_test.main();
// document_sync_test.main();
user_sync_test.main();
anon_user_continue_test.main();
// workspace
@@ -46,7 +46,6 @@ void main() {
await tester.ime.insertText(inputContent);
expect(find.text(inputContent, findRichText: true), findsOneWidget);
// TODO(nathan): remove the await
// 6 seconds for data sync
await tester.waitForSeconds(6);
@@ -2,8 +2,6 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
@@ -12,14 +10,15 @@ import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
@@ -33,69 +32,69 @@ import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final email = '${uuid()}@appflowy.io';
// final email = '${uuid()}@appflowy.io';
group('collaborative workspace', () {
// combine the create and delete workspace test to reduce the time
testWidgets('create a new workspace, open it and then delete it',
(tester) async {
// only run the test when the feature flag is on
if (!FeatureFlag.collaborativeWorkspace.isOn) {
return;
}
// if (!FeatureFlag.collaborativeWorkspace.isOn) {
// return;
// }
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
email: email,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
// await tester.initializeAppFlowy(
// cloudType: AuthenticatorType.appflowyCloudSelfHost,
// email: email,
// );
// await tester.tapGoogleLoginInButton();
// await tester.expectToSeeHomePageWithGetStartedPage();
const name = 'AppFlowy.IO';
// the workspace will be opened after created
await tester.createCollaborativeWorkspace(name);
// const name = 'AppFlowy.IO';
// // the workspace will be opened after created
// await tester.createCollaborativeWorkspace(name);
final loading = find.byType(Loading);
await tester.pumpUntilNotFound(loading);
// final loading = find.byType(Loading);
// await tester.pumpUntilNotFound(loading);
Finder success;
// Finder success;
final Finder items = find.byType(WorkspaceMenuItem);
// final Finder items = find.byType(WorkspaceMenuItem);
// delete the newly created workspace
await tester.openCollaborativeWorkspaceMenu();
await tester.pumpUntilFound(items);
// // delete the newly created workspace
// await tester.openCollaborativeWorkspaceMenu();
// await tester.pumpUntilFound(items);
expect(items, findsNWidgets(2));
expect(
tester.widget<WorkspaceMenuItem>(items.last).workspace.name,
name,
);
// expect(items, findsNWidgets(2));
// expect(
// tester.widget<WorkspaceMenuItem>(items.last).workspace.name,
// name,
// );
final secondWorkspace = find.byType(WorkspaceMenuItem).last;
await tester.hoverOnWidget(
secondWorkspace,
onHover: () async {
// click the more button
final moreButton = find.byType(WorkspaceMoreActionList);
expect(moreButton, findsOneWidget);
await tester.tapButton(moreButton);
// click the delete button
final deleteButton = find.text(LocaleKeys.button_delete.tr());
expect(deleteButton, findsOneWidget);
await tester.tapButton(deleteButton);
// see the delete confirm dialog
final confirm =
find.text(LocaleKeys.workspace_deleteWorkspaceHintText.tr());
expect(confirm, findsOneWidget);
await tester.tapButton(find.text(LocaleKeys.button_ok.tr()));
// delete success
success = find.text(LocaleKeys.workspace_createSuccess.tr());
await tester.pumpUntilFound(success);
expect(success, findsOneWidget);
await tester.pumpUntilNotFound(success);
},
);
// final secondWorkspace = find.byType(WorkspaceMenuItem).last;
// await tester.hoverOnWidget(
// secondWorkspace,
// onHover: () async {
// // click the more button
// final moreButton = find.byType(WorkspaceMoreActionList);
// expect(moreButton, findsOneWidget);
// await tester.tapButton(moreButton);
// // click the delete button
// final deleteButton = find.text(LocaleKeys.button_delete.tr());
// expect(deleteButton, findsOneWidget);
// await tester.tapButton(deleteButton);
// // see the delete confirm dialog
// final confirm =
// find.text(LocaleKeys.workspace_deleteWorkspaceHintText.tr());
// expect(confirm, findsOneWidget);
// await tester.tapButton(find.text(LocaleKeys.button_ok.tr()));
// // delete success
// success = find.text(LocaleKeys.workspace_createSuccess.tr());
// await tester.pumpUntilFound(success);
// expect(success, findsOneWidget);
// await tester.pumpUntilNotFound(success);
// },
// );
});
});
}
@@ -29,6 +29,7 @@ void main() {
},
);
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
await tester.tapOKButton();
expect(find.text(name), findsNothing);
});
@@ -1,10 +1,10 @@
import 'package:integration_test/integration_test.dart';
import 'board_row_test.dart' as board_row_test;
import 'board_add_row_test.dart' as board_add_row_test;
import 'board_group_test.dart' as board_group_test;
import 'board_row_test.dart' as board_row_test;
void startTesting() {
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Board integration tests
@@ -41,15 +41,16 @@ void main() {
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
await tester.hoverOnFirstRowOfGrid();
await tester.hoverOnFirstRowOfGrid(() async {
// Open the row menu and then click the delete
await tester.tapRowMenuButtonInGrid();
await tester.pumpAndSettle();
await tester.tapDeleteOnRowMenu();
await tester.pumpAndSettle();
// Open the row menu and then click the delete
await tester.tapRowMenuButtonInGrid();
await tester.tapDeleteOnRowMenu();
// 3 initial rows - 1 deleted
await tester.assertNumberOfRowsInGridPage(2);
await tester.pumpAndSettle();
// 3 initial rows - 1 deleted
await tester.assertNumberOfRowsInGridPage(2);
});
});
testWidgets('check number of row indicator in the initial grid',
@@ -66,6 +66,7 @@ void main() {
LogicalKeyboardKey.keyR,
],
tester: tester,
withKeyUp: true,
);
expect(first.attributes[blockComponentAlign], rightAlignmentKey);
@@ -77,6 +78,7 @@ void main() {
LogicalKeyboardKey.keyE,
],
tester: tester,
withKeyUp: true,
);
expect(first.attributes[blockComponentAlign], centerAlignmentKey);
@@ -88,6 +90,7 @@ void main() {
LogicalKeyboardKey.keyL,
],
tester: tester,
withKeyUp: true,
);
expect(first.attributes[blockComponentAlign], leftAlignmentKey);
});
@@ -1,10 +1,11 @@
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -116,6 +117,53 @@ void main() {
]);
});
});
testWidgets('paste bulleted list in numbered list', (tester) async {
const inAppJson =
'{"document":{"type":"page","children":[{"type":"bulleted_list","children":[{"type":"bulleted_list","data":{"delta":[{"insert":"World"}]}}],"data":{"delta":[{"insert":"Hello"}]}}]}}';
await tester.pasteContent(
inAppJson: inAppJson,
beforeTest: (editorState) async {
final transaction = editorState.transaction;
// Insert two numbered list nodes
// 1. Parent One
// 2.
transaction.insertNodes(
[0],
[
Node(
type: NumberedListBlockKeys.type,
attributes: {
'delta': [
{"insert": "One"},
],
},
),
Node(
type: NumberedListBlockKeys.type,
attributes: {'delta': []},
),
],
);
// Set the selection to the second numbered list node (which has empty delta)
transaction.afterSelection = Selection.collapsed(Position(path: [1]));
await editorState.apply(transaction);
await tester.pumpAndSettle();
},
(editorState) {
final secondNode = editorState.getNodeAtPath([1]);
expect(secondNode?.delta?.toPlainText(), 'Hello');
expect(secondNode?.children.length, 1);
final childNode = secondNode?.children.first;
expect(childNode?.delta?.toPlainText(), 'World');
expect(childNode?.type, BulletedListBlockKeys.type);
},
);
});
});
testWidgets('paste image(png) from memory', (tester) async {
@@ -210,42 +258,33 @@ void main() {
testWidgets('paste the html content contains section', (tester) async {
const html =
'''<meta charset='utf-8'><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;">AppFlowy</strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;">Hello World</strong></span></section>''';
await tester.pasteContent(
html: html,
(editorState) {
expect(editorState.document.root.children.length, 2);
final node1 = editorState.getNodeAtPath([0])!;
final node2 = editorState.getNodeAtPath([1])!;
expect(node1.type, ParagraphBlockKeys.type);
expect(node2.type, ParagraphBlockKeys.type);
},
);
await tester.pasteContent(html: html, (editorState) {
expect(editorState.document.root.children.length, 2);
final node1 = editorState.getNodeAtPath([0])!;
final node2 = editorState.getNodeAtPath([1])!;
expect(node1.type, ParagraphBlockKeys.type);
expect(node2.type, ParagraphBlockKeys.type);
});
});
testWidgets('paste the html from google translation', (tester) async {
const html =
'''<meta charset='utf-8'><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">new force</font></font></strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">Assessment focus: potential motivations, empathy</font></font></strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><br style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;"></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-Line truncated
await tester.pasteContent(
html: html,
(editorState) {
expect(editorState.document.root.children.length, 8);
},
);
await tester.pasteContent(html: html, (editorState) {
expect(editorState.document.root.children.length, 8);
});
});
testWidgets(
'auto convert url to link preview block',
(widgetTester) async {
(tester) async {
const url = 'https://appflowy.io';
await widgetTester.pasteContent(
plainText: url,
(editorState) {
expect(editorState.document.root.children.length, 2);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, LinkPreviewBlockKeys.type);
expect(node.attributes[LinkPreviewBlockKeys.url], url);
},
);
await tester.pasteContent(plainText: url, (editorState) {
expect(editorState.document.root.children.length, 2);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, LinkPreviewBlockKeys.type);
expect(node.attributes[LinkPreviewBlockKeys.url], url);
});
},
);
}
@@ -256,6 +295,7 @@ extension on WidgetTester {
Future<void> Function(EditorState editorState)? beforeTest,
String? plainText,
String? html,
String? inAppJson,
(String, Uint8List?)? image,
}) async {
await initializeAppFlowy();
@@ -271,6 +311,7 @@ extension on WidgetTester {
ClipboardServiceData(
plainText: plainText,
html: html,
inAppJson: inAppJson,
image: image,
),
);
@@ -111,6 +111,7 @@ Future<void> triggerReferenceDocumentBySlashMenu(WidgetTester tester) async {
LogicalKeyboardKey.enter,
],
tester: tester,
withKeyUp: true,
);
await tester.pumpAndSettle();
@@ -129,6 +130,7 @@ Future<void> enterDocumentText(WidgetTester tester) async {
LogicalKeyboardKey.keyT,
],
tester: tester,
withKeyUp: true,
);
await tester.pumpAndSettle();
}
@@ -19,12 +19,14 @@ void main() {
// Duplicate
await tester.openMoreViewActions();
await tester.duplicateByMoreViewActions();
await tester.pumpAndSettle();
expect(pageFinder, findsNWidgets(2));
// Delete
await tester.openMoreViewActions();
await tester.deleteByMoreViewActions();
await tester.pumpAndSettle();
expect(pageFinder, findsNWidgets(1));
});
@@ -130,24 +130,24 @@ void main() {
final searchEmojiTextField = find.byWidgetPredicate(
(widget) =>
widget is TextField &&
widget.decoration!.hintText == LocaleKeys.emoji_search.tr(),
widget.decoration!.hintText == LocaleKeys.search_label.tr(),
);
await tester.enterText(
searchEmojiTextField,
'hand',
'punch',
);
// change skin tone
await tester.editor.changeEmojiSkinTone(EmojiSkinTone.dark);
// select an icon with skin tone
const hand = '👋🏿';
await tester.tapEmoji(hand);
tester.expectToSeeDocumentIcon(hand);
const punch = '👊🏿';
await tester.tapEmoji(punch);
tester.expectToSeeDocumentIcon(punch);
tester.expectViewHasIcon(
gettingStarted,
ViewLayoutPB.Document,
hand,
punch,
);
});
});
@@ -1,5 +1,5 @@
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -8,8 +8,8 @@ import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('board add row test', () {
testWidgets('Add card from header', (tester) async {
group('notification test', () {
testWidgets('enable notification', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
@@ -17,25 +17,25 @@ void main() {
await tester.openSettingsPage(SettingsPage.notifications);
await tester.pumpAndSettle();
final switchFinder = find.byType(Switch);
final toggleFinder = find.byType(Toggle).first;
// Defaults to enabled
Switch switchWidget = tester.widget(switchFinder);
expect(switchWidget.value, true);
Toggle toggleWidget = tester.widget(toggleFinder);
expect(toggleWidget.value, true);
// Disable
await tester.tap(switchFinder);
await tester.tap(toggleFinder);
await tester.pumpAndSettle();
switchWidget = tester.widget(switchFinder);
expect(switchWidget.value, false);
toggleWidget = tester.widget(toggleFinder);
expect(toggleWidget.value, false);
// Enable again
await tester.tap(switchFinder);
await tester.tap(toggleFinder);
await tester.pumpAndSettle();
switchWidget = tester.widget(switchFinder);
expect(switchWidget.value, true);
toggleWidget = tester.widget(toggleFinder);
expect(toggleWidget.value, true);
});
});
}
@@ -0,0 +1,50 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/auth_operation.dart';
import '../../shared/base.dart';
import '../../shared/expectation.dart';
import '../../shared/settings.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Settings Billing', () {
testWidgets('Local auth cannot see plan+billing', (tester) async {
await tester.initializeAppFlowy();
await tester.tapSignInAsGuest();
await tester.expectToSeeHomePageWithGetStartedPage();
await tester.openSettings();
await tester.pumpAndSettle();
// We check that another settings page is present to ensure
// it's not a fluke
expect(
find.text(
LocaleKeys.settings_workspacePage_menuLabel.tr(),
skipOffstage: false,
),
findsOneWidget,
);
expect(
find.text(
LocaleKeys.settings_planPage_menuLabel.tr(),
skipOffstage: false,
),
findsNothing,
);
expect(
find.text(
LocaleKeys.settings_billingPage_menuLabel.tr(),
skipOffstage: false,
),
findsNothing,
);
});
});
}
@@ -1,9 +1,13 @@
import 'package:integration_test/integration_test.dart';
import 'notifications_settings_test.dart' as notifications_settings_test;
import 'settings_billing_test.dart' as settings_billing_test;
import 'shortcuts_settings_test.dart' as shortcuts_settings_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
notifications_settings_test.main();
settings_billing_test.main();
shortcuts_settings_test.main();
}
@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_shortcuts_view.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/keyboard.dart';
import '../../shared/util.dart';
import '../board/board_hide_groups_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('shortcuts test', () {
testWidgets('can change and overwrite shortcut', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.shortcuts);
await tester.pumpAndSettle();
final backspaceCmd =
LocaleKeys.settings_shortcutsPage_keybindings_backspace.tr();
// Input "Delete" into the search field
await tester.enterText(find.byType(TextField), backspaceCmd);
await tester.pumpAndSettle();
await tester.hoverOnWidget(
find.descendant(
of: find.byType(ShortcutSettingTile),
matching: find.text(backspaceCmd),
),
onHover: () async {
await tester.tap(find.byFlowySvg(FlowySvgs.edit_s));
await tester.pumpAndSettle();
await FlowyTestKeyboard.simulateKeyDownEvent(
[
LogicalKeyboardKey.delete,
LogicalKeyboardKey.enter,
],
tester: tester,
);
await tester.pumpAndSettle();
},
);
// We expect to see conflict dialog
expect(
find.text(
LocaleKeys.settings_shortcutsPage_conflictDialog_confirmLabel.tr(),
),
findsOneWidget,
);
// Press on confirm label
await tester.tap(
find.text(
LocaleKeys.settings_shortcutsPage_conflictDialog_confirmLabel.tr(),
),
);
await tester.pumpAndSettle();
// We expect the first ShortcutSettingTile to have one
// [KeyBadge] with `delete` label
final first = tester.widget(find.byType(ShortcutSettingTile).first)
as ShortcutSettingTile;
expect(
first.command.command,
'delete',
);
// And the second one which is `Delete left character` to have none
// as it will have been overwritten
final second = tester.widget(find.byType(ShortcutSettingTile).at(1))
as ShortcutSettingTile;
expect(
second.command.command,
'',
);
});
});
}
@@ -1,6 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -12,8 +12,8 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('sidebar expand test', () {
bool isExpanded({required FolderCategoryType type}) {
if (type == FolderCategoryType.private) {
bool isExpanded({required FolderSpaceType type}) {
if (type == FolderSpaceType.private) {
return find
.descendant(
of: find.byType(PrivateSectionFolder),
@@ -30,19 +30,19 @@ void main() {
await tester.tapAnonymousSignInButton();
// first time is expanded
expect(isExpanded(type: FolderCategoryType.private), true);
expect(isExpanded(type: FolderSpaceType.private), true);
// collapse the personal folder
await tester.tapButton(
find.byTooltip(LocaleKeys.sideBar_clickToHidePrivate.tr()),
);
expect(isExpanded(type: FolderCategoryType.private), false);
expect(isExpanded(type: FolderSpaceType.private), false);
// expand the personal folder
await tester.tapButton(
find.byTooltip(LocaleKeys.sideBar_clickToHidePrivate.tr()),
);
expect(isExpanded(type: FolderCategoryType.private), true);
expect(isExpanded(type: FolderSpaceType.private), true);
});
});
}
@@ -1,5 +1,5 @@
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/_favorite_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
@@ -46,7 +46,7 @@ void main() {
await tester.favoriteViewByName(names[1]);
expect(
tester.findFavoritePageName(names[1]),
findsNWidgets(2),
findsNWidgets(1),
);
await tester.unfavoriteViewByName(gettingStarted);
@@ -120,9 +120,9 @@ void main() {
(widget) =>
widget is SingleInnerViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite,
widget.spaceType == FolderSpaceType.favorite,
),
findsNWidgets(6),
findsNWidgets(3),
);
await tester.hoverOnPageName(
@@ -135,7 +135,7 @@ void main() {
expect(
tester.findAllFavoritePages(),
findsNWidgets(3),
findsNWidgets(2),
);
await tester.hoverOnPageName(
@@ -168,7 +168,7 @@ void main() {
widget.isSelected != null &&
widget.isSelected!(),
),
findsNWidgets(2),
findsNWidgets(1),
);
},
);
@@ -18,6 +18,9 @@ void main() {
// create document, board, grid and calendar views
for (final value in ViewLayoutPB.values) {
if (value == ViewLayoutPB.Chat) {
continue;
}
await tester.createNewPageWithNameUnderParent(
name: value.name,
parentName: gettingStarted,
@@ -46,6 +49,9 @@ void main() {
// create document, board, grid and calendar views
for (final value in ViewLayoutPB.values) {
if (value == ViewLayoutPB.Chat) {
continue;
}
await tester.createNewPageWithNameUnderParent(
name: value.name,
parentName: gettingStarted,
@@ -39,6 +39,9 @@ void main() {
await tester.tapAnonymousSignInButton();
for (final layout in ViewLayoutPB.values) {
if (layout == ViewLayoutPB.Chat) {
continue;
}
// create a new page
final name = 'AppFlowy_$layout';
await tester.createNewPageWithNameUnderParent(
@@ -66,6 +69,8 @@ void main() {
case ViewLayoutPB.Calendar:
expect(find.byType(CalendarPage), findsOneWidget);
break;
case ViewLayoutPB.Chat:
break;
}
await tester.openPage(gettingStarted);
@@ -4,7 +4,7 @@ import 'sidebar_favorites_test.dart' as sidebar_favorite_test;
import 'sidebar_icon_test.dart' as sidebar_icon_test;
import 'sidebar_test.dart' as sidebar_test;
void startTesting() {
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Sidebar integration tests
@@ -1,4 +1,4 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
@@ -103,8 +103,8 @@ Future<AppFlowyEditor> setUpOpenAITesting(WidgetTester tester) async {
}
Future<void> mockOpenAIRepository() async {
await getIt.unregister<OpenAIRepository>();
getIt.registerFactoryAsync<OpenAIRepository>(
await getIt.unregister<AIRepository>();
getIt.registerFactoryAsync<AIRepository>(
() => Future.value(
MockOpenAIRepository(),
),
@@ -27,7 +27,7 @@ Future<void> runIntegration3OnDesktop() async {
settings_test_runner.main();
share_markdown_test.main();
import_files_test.main();
sidebar_test_runner.startTesting();
board_test_runner.startTesting();
sidebar_test_runner.main();
board_test_runner.main();
tabs_test.main();
}
@@ -5,6 +5,7 @@ import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widget
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -44,6 +45,12 @@ extension AppFlowyAuthTest on WidgetTester {
assert(isSwitched == value);
}
void assertToggleValue(Finder finder, bool value) {
final Toggle switchWidget = widget(finder);
final isSwitched = switchWidget.value;
assert(isSwitched == value);
}
void assertEnableEncryptSwitchValue(bool value) {
assertSwitchValue(
find.descendant(
@@ -65,10 +72,10 @@ extension AppFlowyAuthTest on WidgetTester {
}
void assertAppFlowyCloudEnableSyncSwitchValue(bool value) {
assertSwitchValue(
assertToggleValue(
find.descendant(
of: find.byType(AppFlowyCloudEnableSync),
matching: find.byWidgetPredicate((widget) => widget is Switch),
matching: find.byWidgetPredicate((widget) => widget is Toggle),
),
value,
);
@@ -86,7 +93,7 @@ extension AppFlowyAuthTest on WidgetTester {
Future<void> toggleEnableSync(Type syncButton) async {
final finder = find.descendant(
of: find.byType(syncButton),
matching: find.byWidgetPredicate((widget) => widget is Switch),
matching: find.byWidgetPredicate((widget) => widget is Toggle),
);
await tapButton(finder);
@@ -16,9 +16,9 @@ import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/presentation/screens/screens.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_new_page_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
@@ -27,6 +27,7 @@ import 'package:appflowy/workspace/presentation/notifications/widgets/flowy_tab.
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart';
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_tab_bar.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu.dart';
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/more_view_actions.dart';
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/common_view_action.dart';
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
@@ -60,6 +61,7 @@ extension CommonOperations on WidgetTester {
/// Tap the + button on the home page.
Future<void> tapAddViewButton({
String name = gettingStarted,
ViewLayoutPB layout = ViewLayoutPB.Document,
}) async {
await hoverOnPageName(
name,
@@ -279,7 +281,7 @@ extension CommonOperations on WidgetTester {
bool openAfterCreated = true,
}) async {
// create a new page
await tapAddViewButton(name: parentName ?? gettingStarted);
await tapAddViewButton(name: parentName ?? gettingStarted, layout: layout);
await tapButtonWithName(layout.menuName);
final settingsOrFailure = await getIt<KeyValueStorage>().getWithFormat(
KVKeys.showRenameDialogWhenCreatingNewFile,
@@ -619,6 +621,18 @@ extension SettingsFinder on CommonFinders {
matching: find.byType(Scrollable),
)
.first;
Finder findSettingsMenuScrollable() => find
.descendant(
of: find
.descendant(
of: find.byType(SettingsMenu),
matching: find.byType(SingleChildScrollView),
)
.first,
matching: find.byType(Scrollable),
)
.first;
}
extension ViewLayoutPBTest on ViewLayoutPB {
@@ -130,12 +130,12 @@ extension AppFlowyDatabaseTest on WidgetTester {
await openPage('v020', layout: ViewLayoutPB.Grid);
}
Future<void> hoverOnFirstRowOfGrid() async {
Future<void> hoverOnFirstRowOfGrid([Future<void> Function()? onHover]) async {
final findRow = find.byType(GridRow);
expect(findRow, findsWidgets);
final firstRow = findRow.first;
await hoverOnWidget(firstRow);
await hoverOnWidget(firstRow, onHover: onHover);
}
Future<void> editCell({
@@ -876,11 +876,13 @@ extension AppFlowyDatabaseTest on WidgetTester {
}
Future<void> tapRowMenuButtonInGrid() async {
expect(find.byType(RowMenuButton), findsOneWidget);
await tapButton(find.byType(RowMenuButton));
}
/// Should call [tapRowMenuButtonInGrid] first.
Future<void> tapDeleteOnRowMenu() async {
expect(find.text(LocaleKeys.grid_row_delete.tr()), findsOneWidget);
await tapButtonWithName(LocaleKeys.grid_row_delete.tr());
}
@@ -81,15 +81,12 @@ class EditorOperations {
/// Taps the 'Remove Icon' button in the cover toolbar and the icon popover
Future<void> tapRemoveIconButton({bool isInPicker = false}) async {
Finder button =
find.text(LocaleKeys.document_plugins_cover_removeIcon.tr());
if (isInPicker) {
button = find.descendant(
of: find.byType(FlowyIconPicker),
matching: button,
);
}
final Finder button = !isInPicker
? find.text(LocaleKeys.document_plugins_cover_removeIcon.tr())
: find.descendant(
of: find.byType(FlowyIconPicker),
matching: find.text(LocaleKeys.button_remove.tr()),
);
await tester.tapButton(button);
}
@@ -165,7 +165,7 @@ extension Expectation on WidgetTester {
(widget) =>
widget is SingleInnerViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite &&
widget.spaceType == FolderSpaceType.favorite &&
widget.view.name == name &&
widget.view.layout == layout,
skipOffstage: false,
@@ -175,7 +175,7 @@ extension Expectation on WidgetTester {
(widget) =>
widget is SingleInnerViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite,
widget.spaceType == FolderSpaceType.favorite,
);
Finder findPageName(
@@ -5,10 +5,18 @@ class FlowyTestKeyboard {
static Future<void> simulateKeyDownEvent(
List<LogicalKeyboardKey> keys, {
required flutter_test.WidgetTester tester,
bool withKeyUp = false,
}) async {
for (final LogicalKeyboardKey key in keys) {
await flutter_test.simulateKeyDownEvent(key);
await tester.pumpAndSettle();
}
if (withKeyUp) {
for (final LogicalKeyboardKey key in keys) {
await flutter_test.simulateKeyUpEvent(key);
await tester.pumpAndSettle();
}
}
}
}
@@ -44,7 +44,7 @@ class MockOpenAIRepository extends HttpOpenAIRepository {
required Future<void> Function() onStart,
required Future<void> Function(TextCompletionResponse response) onProcess,
required Future<void> Function() onEnd,
required void Function(OpenAIError error) onError,
required void Function(AIError error) onError,
String? suffix,
int maxTokens = 2048,
double temperature = 0.3,
@@ -1,7 +1,7 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_workspace_view.dart';
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
@@ -32,6 +32,14 @@ extension AppFlowySettings on WidgetTester {
final button = find.byWidgetPredicate(
(widget) => widget is SettingsMenuElement && widget.page == page,
);
await scrollUntilVisible(
button,
0,
scrollable: find.findSettingsMenuScrollable(),
);
await pump();
expect(button, findsOneWidget);
await tapButton(button);
return;
@@ -1,14 +1,14 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'base.dart';
import 'util.dart';
extension AppFlowyWorkspace on WidgetTester {
/// Open workspace menu
@@ -36,12 +36,19 @@ extension AppFlowyWorkspace on WidgetTester {
matching: find.byType(WorkspaceMoreActionList),
);
expect(moreButton, findsOneWidget);
await tapButton(moreButton);
await tapButton(find.findTextInFlowyText(LocaleKeys.button_rename.tr()));
final input = find.byType(TextFormField);
expect(input, findsOneWidget);
await enterText(input, name);
await tapButton(find.text(LocaleKeys.button_ok.tr()));
await hoverOnWidget(
moreButton,
onHover: () async {
await tapButton(moreButton);
await tapButton(
find.findTextInFlowyText(LocaleKeys.button_rename.tr()),
);
final input = find.byType(TextFormField);
expect(input, findsOneWidget);
await enterText(input, name);
await tapButton(find.text(LocaleKeys.button_ok.tr()));
},
);
}
Future<void> changeWorkspaceIcon(String icon) async {
+3 -3
View File
@@ -170,10 +170,10 @@ SPEC CHECKSUMS:
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
integration_test: 13825b8a9334a850581300559b8839134b124670
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
@@ -191,4 +191,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca
COCOAPODS: 1.15.2
COCOAPODS: 1.11.3
@@ -66,5 +66,10 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false />
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
@@ -18,6 +18,13 @@ class KVKeys {
/// {'dx': 10.0, 'dy': 10.0}
static const String windowPosition = 'windowPosition';
/// The key for saving the window status
///
/// The value is a json string with the following format:
/// { 'windowMaximized': true }
///
static const String windowMaximized = 'windowMaximized';
static const String kDocumentAppearanceFontSize =
'kDocumentAppearanceFontSize';
static const String kDocumentAppearanceFontFamily =
@@ -65,6 +72,11 @@ class KVKeys {
/// {'feature_flag_1': true, 'feature_flag_2': false}
static const String featureFlag = 'featureFlag';
/// The key for saving show notification icon option
///
/// The value is a boolean string
static const String showNotificationIcon = 'showNotificationIcon';
/// The key for saving the last opened workspace id
///
/// The workspace id is a string.
@@ -75,4 +87,25 @@ class KVKeys {
///
/// The value is a double string.
static const String scaleFactor = 'scaleFactor';
/// The key for saving the last opened tab (favorite, recent, space etc.)
///
/// The value is a int string.
static const String lastOpenedSpace = 'lastOpenedSpace';
/// The key for saving the space tab order
///
/// The value is a json string with the following format:
/// [0, 1, 2]
static const String spaceOrder = 'spaceOrder';
/// The key for saving the last opened space id (space A, space B)
///
/// The value is a string.
static const String lastOpenedSpaceId = 'lastOpenedSpaceId';
/// The key for saving the upgrade space tag
///
/// The value is a boolean string
static const String hasUpgradedSpace = 'hasUpgradedSpace060';
}
@@ -1,14 +1,16 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/window_title_bar.dart';
import 'package:appflowy/util/theme_extension.dart';
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CocoaWindowChannel {
@@ -102,22 +104,40 @@ class MoveWindowDetectorState extends State<MoveWindowDetector> {
return const SizedBox.shrink();
}
final color = Theme.of(context).isLightMode ? Colors.white : Colors.black;
final textSpan = TextSpan(
children: [
TextSpan(
text: '${LocaleKeys.sideBar_openSidebar.tr()}\n',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: color),
),
TextSpan(
text: Platform.isMacOS ? '⌘+.' : 'Ctrl+\\',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Theme.of(context).hintColor),
),
],
);
return FlowyTooltip(
richMessage: TextSpan(
children: [
TextSpan(text: '${LocaleKeys.sideBar_closeSidebar.tr()}\n'),
const TextSpan(text: 'Ctrl+\\'),
],
),
child: FlowyIconButton(
hoverColor: Colors.transparent,
onPressed: () => context
richMessage: textSpan,
child: Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: (_) => context
.read<HomeSettingBloc>()
.add(const HomeSettingEvent.collapseMenu()),
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
icon: context.read<HomeSettingBloc>().state.isMenuCollapsed
? const FlowySvg(FlowySvgs.show_menu_s)
: const FlowySvg(FlowySvgs.hide_menu_m),
child: FlowyHover(
child: Container(
width: 24,
padding: const EdgeInsets.all(4),
child: const RotatedBox(
quarterTurns: 2,
child: FlowySvg(FlowySvgs.hide_menu_s),
),
),
),
),
);
}
@@ -1,9 +1,10 @@
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_backend/log.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:string_validator/string_validator.dart';
import 'package:url_launcher/url_launcher.dart' as launcher;
@@ -481,7 +481,7 @@ class _AFDropdownMenuState<T> extends State<AFDropdownMenu<T>> {
ButtonStyle effectiveStyle = entry.style ?? defaultStyle;
final Color focusedBackgroundColor = effectiveStyle.foregroundColor
?.resolve(<MaterialState>{MaterialState.focused}) ??
?.resolve(<WidgetState>{WidgetState.focused}) ??
Theme.of(context).colorScheme.onSurface;
Widget label = entry.labelWidget ?? Text(entry.label);
@@ -499,7 +499,7 @@ class _AFDropdownMenuState<T> extends State<AFDropdownMenu<T>> {
// color will also change to foregroundColor.withOpacity(0.12).
effectiveStyle = entry.enabled && i == focusedIndex
? effectiveStyle.copyWith(
backgroundColor: MaterialStatePropertyAll<Color>(
backgroundColor: WidgetStatePropertyAll<Color>(
focusedBackgroundColor.withOpacity(0.12),
),
)
@@ -628,17 +628,17 @@ class _AFDropdownMenuState<T> extends State<AFDropdownMenu<T>> {
final double? anchorWidth = getWidth(_anchorKey);
if (widget.width != null) {
effectiveMenuStyle = effectiveMenuStyle.copyWith(
minimumSize: MaterialStatePropertyAll<Size?>(Size(widget.width!, 0.0)),
minimumSize: WidgetStatePropertyAll<Size?>(Size(widget.width!, 0.0)),
);
} else if (anchorWidth != null) {
effectiveMenuStyle = effectiveMenuStyle.copyWith(
minimumSize: MaterialStatePropertyAll<Size?>(Size(anchorWidth, 0.0)),
minimumSize: WidgetStatePropertyAll<Size?>(Size(anchorWidth, 0.0)),
);
}
if (widget.menuHeight != null) {
effectiveMenuStyle = effectiveMenuStyle.copyWith(
maximumSize: MaterialStatePropertyAll<Size>(
maximumSize: WidgetStatePropertyAll<Size>(
Size(double.infinity, widget.menuHeight!),
),
);
@@ -1029,8 +1029,8 @@ class _DropdownMenuDefaultsM3 extends DropdownMenuThemeData {
@override
MenuStyle get menuStyle {
return const MenuStyle(
minimumSize: MaterialStatePropertyAll<Size>(Size(_kMinimumWidth, 0.0)),
maximumSize: MaterialStatePropertyAll<Size>(Size.infinite),
minimumSize: WidgetStatePropertyAll<Size>(Size(_kMinimumWidth, 0.0)),
maximumSize: WidgetStatePropertyAll<Size>(Size.infinite),
visualDensity: VisualDensity.standard,
);
}
@@ -1,5 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'package:appflowy/mobile/presentation/chat/mobile_chat_screen.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:flutter/material.dart';
@@ -14,15 +16,15 @@ import 'package:go_router/go_router.dart';
extension MobileRouter on BuildContext {
Future<void> pushView(ViewPB view, [Map<String, dynamic>? arguments]) async {
await push(
Uri(
path: view.routeName,
queryParameters: view.queryParameters(arguments),
).toString(),
).then((value) {
getIt<MenuSharedState>().latestOpenView = view;
getIt<CachedRecentService>().updateRecentViews([view.id], true);
});
// set the current view before pushing the new view
getIt<MenuSharedState>().latestOpenView = view;
unawaited(getIt<CachedRecentService>().updateRecentViews([view.id], true));
final uri = Uri(
path: view.routeName,
queryParameters: view.queryParameters(arguments),
).toString();
await push(uri);
}
}
@@ -37,6 +39,9 @@ extension on ViewPB {
return MobileCalendarScreen.routeName;
case ViewLayoutPB.Board:
return MobileBoardScreen.routeName;
case ViewLayoutPB.Chat:
return MobileChatScreen.routeName;
default:
throw UnimplementedError('routeName for $this is not implemented');
}
@@ -65,6 +70,11 @@ extension on ViewPB {
MobileBoardScreen.viewId: id,
MobileBoardScreen.viewTitle: name,
};
case ViewLayoutPB.Chat:
return {
MobileChatScreen.viewId: id,
MobileChatScreen.viewTitle: name,
};
default:
throw UnimplementedError(
'queryParameters for $this is not implemented',
@@ -57,7 +57,19 @@ class RecentViewBloc extends Bloc<RecentViewEvent, RecentViewState> {
}
},
);
// only document supports the cover
if (view.layout != ViewLayoutPB.Document) {
emit(
state.copyWith(
name: view.name,
icon: view.icon.value,
),
);
}
final cover = getCoverV2();
if (cover != null) {
emit(
state.copyWith(
@@ -6,8 +6,8 @@ import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart';
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
import 'package:appflowy/plugins/document/presentation/document_collaborators.dart';
import 'package:appflowy/plugins/shared/sync_indicator.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
@@ -154,7 +154,10 @@ class _MobileViewPageState extends State<MobileViewPage> {
(view) {
final plugin = view.plugin(arguments: widget.arguments ?? const {})
..init();
return plugin.widgetBuilder.buildWidget(shrinkWrap: false);
return plugin.widgetBuilder.buildWidget(
shrinkWrap: false,
context: PluginContext(userProfile: state.userProfilePB),
);
},
(error) {
return FlowyMobileStateContainer.error(
@@ -191,13 +194,6 @@ class _MobileViewPageState extends State<MobileViewPage> {
padding: const EdgeInsets.symmetric(vertical: 8),
view: view,
),
const HSpace(16.0),
DocumentSyncIndicator(view: view),
const HSpace(12.0),
]);
} else {
actions.addAll([
DatabaseSyncIndicator(view: view),
const HSpace(12.0),
]);
}
@@ -1,5 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class TypeOptionMenuItemValue<T> {
@@ -12,6 +12,7 @@ import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -92,7 +93,7 @@ class MobileViewPageMoreButton extends StatelessWidget {
context,
showDragHandle: true,
showDivider: false,
backgroundColor: Theme.of(context).colorScheme.background,
backgroundColor: AFThemeExtension.of(context).background,
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(value: context.read<ViewBloc>()),
@@ -144,7 +145,7 @@ class MobileViewPageLayoutButton extends StatelessWidget {
showDoneButton: true,
showHeader: true,
title: LocaleKeys.pageStyle_title.tr(),
backgroundColor: Theme.of(context).colorScheme.background,
backgroundColor: AFThemeExtension.of(context).background,
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(value: context.read<DocumentPageStyleBloc>()),
@@ -38,6 +38,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
case MobileViewBottomSheetBodyAction.removeFromFavorites:
context.pop();
context.read<FavoriteBloc>().add(FavoriteEvent.toggle(view));
break;
case MobileViewBottomSheetBodyAction.undo:
EditorNotification.undo().post();
@@ -1,4 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@@ -19,7 +20,7 @@ class BottomSheetActionWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final iconColor =
this.iconColor ?? Theme.of(context).colorScheme.onBackground;
this.iconColor ?? AFThemeExtension.of(context).onBackground;
if (svg == null) {
return OutlinedButton(
@@ -21,40 +21,54 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
children: [
FlowyOptionTile.text(
text: LocaleKeys.document_menuName.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.document_s,
size: Size.square(20),
FlowySvgs.icon_document_s,
size: Size.square(18),
),
showTopBorder: false,
onTap: () => onAction(ViewLayoutPB.Document),
),
FlowyOptionTile.text(
text: LocaleKeys.grid_menuName.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.grid_s,
size: Size.square(20),
FlowySvgs.icon_grid_s,
size: Size.square(18),
),
showTopBorder: false,
onTap: () => onAction(ViewLayoutPB.Grid),
),
FlowyOptionTile.text(
text: LocaleKeys.board_menuName.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.board_s,
size: Size.square(20),
FlowySvgs.icon_board_s,
size: Size.square(18),
),
showTopBorder: false,
onTap: () => onAction(ViewLayoutPB.Board),
),
FlowyOptionTile.text(
text: LocaleKeys.calendar_menuName.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.date_s,
size: Size.square(20),
FlowySvgs.icon_calendar_s,
size: Size.square(18),
),
showTopBorder: false,
onTap: () => onAction(ViewLayoutPB.Calendar),
),
FlowyOptionTile.text(
text: LocaleKeys.chat_newChat.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.chat_ai_page_s,
size: Size.square(18),
),
showTopBorder: false,
onTap: () => onAction(ViewLayoutPB.Chat),
),
],
);
}
@@ -33,6 +33,7 @@ class BlockActionBottomSheet extends StatelessWidget {
FlowySvgs.arrow_up_s,
size: Size.square(20),
),
showTopBorder: false,
onTap: () => onAction(BlockActionBottomSheetType.insertAbove),
),
FlowyOptionTile.text(
@@ -48,7 +49,7 @@ class BlockActionBottomSheet extends StatelessWidget {
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_duplicate.tr(),
leftIcon: const FlowySvg(FlowySvgs.m_field_copy_s),
leftIcon: const FlowySvg(FlowySvgs.m_duplicate_s),
onTap: () => onAction(BlockActionBottomSheetType.duplicate),
),
@@ -1,9 +1,17 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
import 'package:appflowy/startup/tasks/app_widget.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
enum MobileBottomSheetType {
view,
@@ -14,11 +22,13 @@ class MobileViewItemBottomSheet extends StatefulWidget {
const MobileViewItemBottomSheet({
super.key,
required this.view,
required this.actions,
this.defaultType = MobileBottomSheetType.view,
});
final ViewPB view;
final MobileBottomSheetType defaultType;
final List<MobileViewItemBottomSheetBodyAction> actions;
@override
State<MobileViewItemBottomSheet> createState() =>
@@ -27,12 +37,14 @@ class MobileViewItemBottomSheet extends StatefulWidget {
class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
MobileBottomSheetType type = MobileBottomSheetType.view;
final fToast = FToast();
@override
void initState() {
super.initState();
type = widget.defaultType;
fToast.init(AppGlobals.context);
}
@override
@@ -40,6 +52,7 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
switch (type) {
case MobileBottomSheetType.view:
return MobileViewItemBottomSheetBody(
actions: widget.actions,
isFavorite: widget.view.isFavorite,
onAction: (action) {
switch (action) {
@@ -59,7 +72,6 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
case MobileViewItemBottomSheetBodyAction.delete:
Navigator.pop(context);
context.read<ViewBloc>().add(const ViewEvent.delete());
break;
case MobileViewItemBottomSheetBodyAction.addToFavorites:
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
@@ -68,6 +80,11 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(widget.view));
break;
case MobileViewItemBottomSheetBodyAction.removeFromRecent:
_removeFromRecent(context);
break;
case MobileViewItemBottomSheetBodyAction.divider:
break;
}
},
);
@@ -83,4 +100,74 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
);
}
}
Future<void> _removeFromRecent(BuildContext context) async {
final viewId = context.read<ViewBloc>().view.id;
final recentViewsBloc = context.read<RecentViewsBloc>();
Navigator.pop(context);
await _showConfirmDialog(
onDelete: () {
recentViewsBloc.add(RecentViewsEvent.removeRecentViews([viewId]));
fToast.showToast(
child: const _RemoveToast(),
positionedToastBuilder: (context, child) {
return Positioned.fill(
top: 450,
child: child,
);
},
);
},
);
}
Future<void> _showConfirmDialog({required VoidCallback onDelete}) async {
await showFlowyCupertinoConfirmDialog(
title: LocaleKeys.sideBar_removePageFromRecent.tr(),
leftButton: FlowyText.regular(
LocaleKeys.button_cancel.tr(),
color: const Color(0xFF1456F0),
),
rightButton: FlowyText.medium(
LocaleKeys.button_delete.tr(),
color: const Color(0xFFFE0220),
),
onRightButtonPressed: (context) {
onDelete();
Navigator.pop(context);
},
);
}
}
class _RemoveToast extends StatelessWidget {
const _RemoveToast();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
color: const Color(0xE5171717),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const FlowySvg(
FlowySvgs.success_s,
blendMode: null,
),
const HSpace(8.0),
FlowyText.regular(
LocaleKeys.sideBar_removeSuccess.tr(),
fontSize: 16.0,
color: Colors.white,
),
],
),
);
}
}
@@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -11,6 +11,8 @@ enum MobileViewItemBottomSheetBodyAction {
delete,
addToFavorites,
removeFromFavorites,
divider,
removeFromRecent,
}
class MobileViewItemBottomSheetBody extends StatelessWidget {
@@ -18,63 +20,134 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
super.key,
this.isFavorite = false,
required this.onAction,
required this.actions,
});
final bool isFavorite;
final void Function(MobileViewItemBottomSheetBodyAction action) onAction;
final List<MobileViewItemBottomSheetBodyAction> actions;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
MobileQuickActionButton(
text: LocaleKeys.button_rename.tr(),
icon: FlowySvgs.m_rename_s,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.rename,
),
),
_divider(),
MobileQuickActionButton(
text: isFavorite
? LocaleKeys.button_removeFromFavorites.tr()
: LocaleKeys.button_addToFavorites.tr(),
icon: isFavorite
? FlowySvgs.m_favorite_selected_lg
: FlowySvgs.m_favorite_unselected_lg,
iconColor: isFavorite ? Colors.yellow : null,
onTap: () => onAction(
isFavorite
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites,
),
),
_divider(),
MobileQuickActionButton(
text: LocaleKeys.button_duplicate.tr(),
icon: FlowySvgs.m_duplicate_s,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.duplicate,
),
),
_divider(),
MobileQuickActionButton(
text: LocaleKeys.button_delete.tr(),
textColor: Theme.of(context).colorScheme.error,
icon: FlowySvgs.m_delete_s,
iconColor: Theme.of(context).colorScheme.error,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.delete,
),
),
_divider(),
],
children:
actions.map((action) => _buildActionButton(context, action)).toList(),
);
}
Widget _divider() => const Divider(
height: 8.5,
thickness: 0.5,
);
Widget _buildActionButton(
BuildContext context,
MobileViewItemBottomSheetBodyAction action,
) {
switch (action) {
case MobileViewItemBottomSheetBodyAction.rename:
return FlowyOptionTile.text(
text: LocaleKeys.button_rename.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.view_item_rename_s,
size: Size.square(18),
),
showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.rename,
),
);
case MobileViewItemBottomSheetBodyAction.duplicate:
return FlowyOptionTile.text(
text: LocaleKeys.button_duplicate.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.duplicate_s,
size: Size.square(18),
),
showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.duplicate,
),
);
case MobileViewItemBottomSheetBodyAction.share:
return FlowyOptionTile.text(
text: LocaleKeys.button_share.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.share_s,
size: Size.square(18),
),
showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.share,
),
);
case MobileViewItemBottomSheetBodyAction.delete:
return FlowyOptionTile.text(
text: LocaleKeys.button_delete.tr(),
height: 52.0,
textColor: Theme.of(context).colorScheme.error,
leftIcon: FlowySvg(
FlowySvgs.trash_s,
size: const Size.square(18),
color: Theme.of(context).colorScheme.error,
),
showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.delete,
),
);
case MobileViewItemBottomSheetBodyAction.addToFavorites:
return FlowyOptionTile.text(
height: 52.0,
text: LocaleKeys.button_addToFavorites.tr(),
leftIcon: const FlowySvg(
FlowySvgs.favorite_s,
size: Size.square(18),
),
showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.addToFavorites,
),
);
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
return FlowyOptionTile.text(
height: 52.0,
text: LocaleKeys.button_removeFromFavorites.tr(),
leftIcon: const FlowySvg(
FlowySvgs.favorite_section_remove_from_favorite_s,
size: Size.square(18),
),
showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.removeFromFavorites,
),
);
case MobileViewItemBottomSheetBodyAction.removeFromRecent:
return FlowyOptionTile.text(
height: 52.0,
text: LocaleKeys.button_removeFromRecent.tr(),
leftIcon: const FlowySvg(
FlowySvgs.remove_from_recent_s,
size: Size.square(18),
),
showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.removeFromRecent,
),
);
case MobileViewItemBottomSheetBodyAction.divider:
return const Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Divider(height: 0.5),
);
}
}
}
@@ -89,7 +89,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
children: [
MobileQuickActionButton(
text: LocaleKeys.button_rename.tr(),
icon: FlowySvgs.m_rename_s,
icon: FlowySvgs.view_item_rename_s,
onTap: () => onAction(
MobileViewBottomSheetBodyAction.rename,
),
@@ -99,10 +99,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
text: isFavorite
? LocaleKeys.button_removeFromFavorites.tr()
: LocaleKeys.button_addToFavorites.tr(),
icon: isFavorite
? FlowySvgs.m_favorite_selected_lg
: FlowySvgs.m_favorite_unselected_lg,
iconColor: isFavorite ? Colors.yellow : null,
icon: isFavorite ? FlowySvgs.unfavorite_s : FlowySvgs.favorite_s,
onTap: () => onAction(
isFavorite
? MobileViewBottomSheetBodyAction.removeFromFavorites
@@ -112,7 +109,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
_divider(),
MobileQuickActionButton(
text: LocaleKeys.button_duplicate.tr(),
icon: FlowySvgs.m_duplicate_s,
icon: FlowySvgs.duplicate_s,
onTap: () => onAction(
MobileViewBottomSheetBodyAction.duplicate,
),
@@ -121,7 +118,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
MobileQuickActionButton(
text: LocaleKeys.button_delete.tr(),
textColor: Theme.of(context).colorScheme.error,
icon: FlowySvgs.m_delete_s,
icon: FlowySvgs.trash_s,
iconColor: Theme.of(context).colorScheme.error,
onTap: () => onAction(
MobileViewBottomSheetBodyAction.delete,
@@ -1,8 +1,15 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/home/shared/mobile_page_card.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_slide_action_button.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
@@ -11,11 +18,14 @@ enum MobilePaneActionType {
delete,
addToFavorites,
removeFromFavorites,
more;
more,
add;
MobileSlideActionButton actionButton(
BuildContext context,
) {
BuildContext context, {
MobilePageCardType? cardType,
FolderSpaceType? spaceType,
}) {
switch (this) {
case MobilePaneActionType.delete:
return MobileSlideActionButton(
@@ -27,45 +37,89 @@ enum MobilePaneActionType {
);
case MobilePaneActionType.removeFromFavorites:
return MobileSlideActionButton(
backgroundColor: Colors.orange,
svg: FlowySvgs.favorite_s,
backgroundColor: const Color(0xFFFA217F),
svg: FlowySvgs.favorite_section_remove_from_favorite_s,
size: 24.0,
onPressed: (context) => context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
);
case MobilePaneActionType.addToFavorites:
return MobileSlideActionButton(
backgroundColor: Colors.orange,
svg: FlowySvgs.m_favorite_unselected_lg,
size: 34.0,
backgroundColor: const Color(0xFF00C8FF),
svg: FlowySvgs.favorite_s,
size: 24.0,
onPressed: (context) => context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
);
case MobilePaneActionType.more:
case MobilePaneActionType.add:
return MobileSlideActionButton(
backgroundColor: Colors.grey,
svg: FlowySvgs.three_dots_vertical_s,
backgroundColor: const Color(0xFF00C8FF),
svg: FlowySvgs.add_m,
size: 28.0,
onPressed: (context) {
final viewBloc = context.read<ViewBloc>();
final view = viewBloc.state.view;
final title = view.name;
showMobileBottomSheet(
context,
showHeader: true,
title: title,
showDragHandle: true,
showCloseButton: true,
useRootNavigator: true,
backgroundColor: Theme.of(context).colorScheme.surface,
builder: (sheetContext) {
return AddNewPageWidgetBottomSheet(
view: view,
onAction: (layout) {
Navigator.of(sheetContext).pop();
viewBloc.add(
ViewEvent.createView(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout,
section: spaceType!.toViewSectionPB,
),
);
},
);
},
);
},
);
case MobilePaneActionType.more:
return MobileSlideActionButton(
backgroundColor: const Color(0xE5515563),
svg: FlowySvgs.three_dots_s,
size: 24.0,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
onPressed: (context) {
final viewBloc = context.read<ViewBloc>();
final favoriteBloc = context.read<FavoriteBloc>();
final recentViewsBloc = context.read<RecentViewsBloc?>();
showMobileBottomSheet(
context,
showDragHandle: true,
showDivider: false,
backgroundColor: Theme.of(context).colorScheme.background,
useRootNavigator: true,
backgroundColor: Theme.of(context).colorScheme.surface,
builder: (context) {
return MultiBlocProvider(
providers: [
BlocProvider.value(value: viewBloc),
BlocProvider.value(value: favoriteBloc),
if (recentViewsBloc != null)
BlocProvider.value(value: recentViewsBloc),
],
child: BlocBuilder<ViewBloc, ViewState>(
builder: (context, state) {
return MobileViewItemBottomSheet(
view: viewBloc.state.view,
actions: _buildActions(state.view, cardType: cardType),
);
},
),
@@ -76,19 +130,70 @@ enum MobilePaneActionType {
);
}
}
List<MobileViewItemBottomSheetBodyAction> _buildActions(
ViewPB view, {
MobilePageCardType? cardType,
}) {
final isFavorite = view.isFavorite;
if (cardType != null) {
switch (cardType) {
case MobilePageCardType.recent:
return [
isFavorite
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites,
MobileViewItemBottomSheetBodyAction.divider,
if (view.layout != ViewLayoutPB.Chat)
MobileViewItemBottomSheetBodyAction.duplicate,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.removeFromRecent,
];
case MobilePageCardType.favorite:
return [
isFavorite
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.duplicate,
];
}
}
return [
isFavorite
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.rename,
if (view.layout != ViewLayoutPB.Chat)
MobileViewItemBottomSheetBodyAction.duplicate,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.delete,
];
}
}
ActionPane buildEndActionPane(
BuildContext context,
List<MobilePaneActionType> actions,
) {
List<MobilePaneActionType> actions, {
bool needSpace = true,
MobilePageCardType? cardType,
FolderSpaceType? spaceType,
}) {
return ActionPane(
motion: const ScrollMotion(),
extentRatio: actions.length / 5,
children: actions
.map(
(action) => action.actionButton(context),
)
.toList(),
children: [
if (needSpace) const HSpace(20),
...actions.map(
(action) => action.actionButton(
context,
spaceType: spaceType,
cardType: cardType,
),
),
],
);
}
@@ -1,6 +1,6 @@
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
extension BottomSheetPaddingExtension on BuildContext {
@@ -0,0 +1,28 @@
import 'package:appflowy/mobile/presentation/base/mobile_view_page.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter/material.dart';
class MobileChatScreen extends StatelessWidget {
const MobileChatScreen({
super.key,
required this.id,
this.title,
});
/// view id
final String id;
final String? title;
static const routeName = '/chat';
static const viewId = 'id';
static const viewTitle = 'title';
@override
Widget build(BuildContext context) {
return MobileViewPage(
id: id,
title: title,
viewLayout: ViewLayoutPB.Chat,
);
}
}
@@ -15,6 +15,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_board/appflowy_board.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -265,7 +266,7 @@ class _BoardContentState extends State<_BoardContent> {
BoxDecoration _makeBoxDecoration(BuildContext context) {
final themeMode = context.read<AppearanceSettingsCubit>().state.themeMode;
return BoxDecoration(
color: Theme.of(context).colorScheme.background,
color: AFThemeExtension.of(context).background,
borderRadius: const BorderRadius.all(Radius.circular(8)),
border: themeMode == ThemeMode.light
? Border.fromBorderSide(
@@ -60,7 +60,7 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
child: IconButton(
icon: Icon(
Icons.close,
color: style.colorScheme.onBackground,
color: style.colorScheme.onSurface,
),
onPressed: () =>
setState(() => _textController.clear()),
@@ -86,7 +86,7 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
child: Text(
LocaleKeys.button_cancel.tr(),
style: style.textTheme.titleSmall?.copyWith(
color: style.colorScheme.onBackground,
color: style.colorScheme.onSurface,
),
),
onPressed: () => setState(() => isEditing = false),
@@ -96,7 +96,7 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
LocaleKeys.button_add.tr(),
style: style.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
color: style.colorScheme.onBackground,
color: style.colorScheme.onSurface,
),
),
onPressed: () {
@@ -117,14 +117,14 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
)
: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
foregroundColor: style.colorScheme.onBackground,
foregroundColor: style.colorScheme.onSurface,
backgroundColor: style.colorScheme.secondary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
).copyWith(
overlayColor:
MaterialStateProperty.all(Theme.of(context).hoverColor),
WidgetStateProperty.all(Theme.of(context).hoverColor),
),
icon: const Icon(Icons.add),
label: Text(
@@ -9,6 +9,7 @@ import 'package:appflowy/plugins/database/widgets/cell/card_cell_skeleton/text_c
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -168,7 +169,7 @@ class MobileHiddenGroup extends StatelessWidget {
return TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.bodyMedium,
foregroundColor: Theme.of(context).colorScheme.onBackground,
foregroundColor: AFThemeExtension.of(context).onBackground,
visualDensity: VisualDensity.compact,
),
child: CardCellBuilder(
@@ -20,6 +20,7 @@ import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'
import 'package:appflowy/plugins/database/widgets/row/row_property.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -131,7 +132,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
void _showCardActions(BuildContext context) {
showMobileBottomSheet(
context,
backgroundColor: Theme.of(context).colorScheme.background,
backgroundColor: AFThemeExtension.of(context).background,
showDragHandle: true,
builder: (_) => Column(
mainAxisSize: MainAxisSize.min,
@@ -139,7 +140,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
MobileQuickActionButton(
onTap: () =>
_performAction(viewId, _bloc.state.currentRowId, false),
icon: FlowySvgs.copy_s,
icon: FlowySvgs.duplicate_s,
text: LocaleKeys.button_duplicate.tr(),
),
const Divider(height: 8.5, thickness: 0.5),
@@ -147,7 +148,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
onTap: () => _performAction(viewId, _bloc.state.currentRowId, true),
text: LocaleKeys.button_delete.tr(),
textColor: Theme.of(context).colorScheme.error,
icon: FlowySvgs.m_delete_m,
icon: FlowySvgs.trash_s,
iconColor: Theme.of(context).colorScheme.error,
),
const Divider(height: 8.5, thickness: 0.5),
@@ -2,6 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_sheets.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@@ -19,20 +20,23 @@ class MobileRowDetailCreateFieldButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(minWidth: double.infinity),
constraints: BoxConstraints(
minWidth: double.infinity,
minHeight: GridSize.headerHeight,
),
child: TextButton.icon(
style: Theme.of(context).textButtonTheme.style?.copyWith(
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
),
overlayColor: MaterialStateProperty.all<Color>(
overlayColor: WidgetStateProperty.all<Color>(
Theme.of(context).hoverColor,
),
alignment: AlignmentDirectional.centerStart,
splashFactory: NoSplash.splashFactory,
padding: const MaterialStatePropertyAll(
padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(vertical: 14, horizontal: 6),
),
),
@@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/type_option_menu_item.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
@@ -7,7 +9,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da
import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:go_router/go_router.dart';
import 'mobile_create_field_screen.dart';
@@ -27,6 +29,7 @@ const mobileSupportedFieldTypes = [
FieldType.CreatedTime,
FieldType.Checkbox,
FieldType.Checklist,
// FieldType.Time,
];
Future<FieldType?> showFieldTypeGridBottomSheet(
@@ -40,7 +43,7 @@ Future<FieldType?> showFieldTypeGridBottomSheet(
showCloseButton: true,
elevation: 20,
title: title,
backgroundColor: Theme.of(context).colorScheme.background,
backgroundColor: AFThemeExtension.of(context).background,
enableDraggableScrollable: true,
builder: (context) {
final typeOptionMenuItemValue = mobileSupportedFieldTypes
@@ -119,6 +119,7 @@ class FieldOptionValues {
case FieldType.RichText:
case FieldType.URL:
case FieldType.Checkbox:
case FieldType.Time:
return null;
case FieldType.Number:
return NumberTypeOptionPB(
@@ -5,8 +5,8 @@ import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/application/field/field_editor_bloc.dart';
import 'package:appflowy/plugins/database/domain/field_backend_service.dart';
import 'package:appflowy/plugins/database/application/field/field_info.dart';
import 'package:appflowy/plugins/database/domain/field_backend_service.dart';
import 'package:appflowy/plugins/database/widgets/setting/field_visibility_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
@@ -438,7 +438,7 @@ class _SortDetailContent extends StatelessWidget {
color: Theme.of(context).colorScheme.surface,
),
splashFactory: NoSplash.splashFactory,
overlayColor: const MaterialStatePropertyAll(
overlayColor: const WidgetStatePropertyAll(
Colors.transparent,
),
onTap: (index) {
@@ -11,6 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -183,7 +184,7 @@ class MobileDatabaseViewListButton extends StatelessWidget {
showMobileBottomSheet(
context,
showDragHandle: true,
backgroundColor: Theme.of(context).colorScheme.background,
backgroundColor: AFThemeExtension.of(context).background,
builder: (_) {
return BlocProvider<ViewBloc>(
create: (_) =>
@@ -107,9 +107,9 @@ enum _Action {
FlowySvgData get icon {
return switch (this) {
edit => FlowySvgs.edit_s,
duplicate => FlowySvgs.copy_s,
delete => FlowySvgs.delete_s,
edit => FlowySvgs.view_item_rename_s,
duplicate => FlowySvgs.duplicate_s,
delete => FlowySvgs.trash_s,
};
}
@@ -234,7 +234,6 @@ class DatabaseViewSettingTile extends StatelessWidget {
showHeader: true,
showBackButton: true,
title: LocaleKeys.grid_settings_properties.tr(),
showDivider: true,
builder: (_) {
return BlocProvider.value(
value: context.read<ViewBloc>(),
@@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart';
@@ -10,6 +8,7 @@ import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
@@ -17,14 +16,15 @@ class MobileFavoritePageFolder extends StatelessWidget {
const MobileFavoritePageFolder({
super.key,
required this.userProfile,
required this.workspaceId,
});
final UserProfilePB userProfile;
final String workspaceId;
@override
Widget build(BuildContext context) {
final workspaceId =
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
'';
return MultiBlocProvider(
providers: [
BlocProvider(
@@ -67,7 +67,8 @@ class MobileFavoritePageFolder extends StatelessWidget {
MobileFavoriteFolder(
showHeader: false,
forceExpanded: true,
views: favoriteState.views,
views:
favoriteState.views.map((e) => e.item).toList(),
),
const VSpace(100.0),
],
@@ -64,8 +64,6 @@ class MobileFavoriteScreen extends StatelessWidget {
builder: (context, state) {
return MobileFavoritePage(
userProfile: userProfile,
workspaceId: state.currentWorkspace?.workspaceId ??
workspaceSetting.workspaceId,
);
},
),
@@ -81,11 +79,9 @@ class MobileFavoritePage extends StatelessWidget {
const MobileFavoritePage({
super.key,
required this.userProfile,
required this.workspaceId,
});
final UserProfilePB userProfile;
final String workspaceId;
@override
Widget build(BuildContext context) {
@@ -108,7 +104,6 @@ class MobileFavoritePage extends StatelessWidget {
Expanded(
child: MobileFavoritePageFolder(
userProfile: userProfile,
workspaceId: workspaceId,
),
),
],
@@ -0,0 +1,133 @@
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/shared/empty_placeholder.dart';
import 'package:appflowy/mobile/presentation/home/shared/mobile_page_card.dart';
import 'package:appflowy/util/theme_extension.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/user/prelude.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileFavoriteSpace extends StatefulWidget {
const MobileFavoriteSpace({
super.key,
required this.userProfile,
});
final UserProfilePB userProfile;
@override
State<MobileFavoriteSpace> createState() => _MobileFavoriteSpaceState();
}
class _MobileFavoriteSpaceState extends State<MobileFavoriteSpace>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
final workspaceId =
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
'';
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => SidebarSectionsBloc()
..add(
SidebarSectionsEvent.initial(widget.userProfile, workspaceId),
),
),
BlocProvider(
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
),
],
child: BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
listener: (context, state) =>
context.read<FavoriteBloc>().add(const FavoriteEvent.initial()),
child: MultiBlocListener(
listeners: [
BlocListener<SidebarSectionsBloc, SidebarSectionsState>(
listenWhen: (p, c) =>
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
listener: (context, state) =>
context.pushView(state.lastCreatedRootView!),
),
],
child: Builder(
builder: (context) {
final favoriteState = context.watch<FavoriteBloc>().state;
if (favoriteState.isLoading) {
return const SizedBox.shrink();
}
if (favoriteState.views.isEmpty) {
return const EmptySpacePlaceholder(
type: MobilePageCardType.favorite,
);
}
return _FavoriteViews(
favoriteViews: favoriteState.views.reversed.toList(),
);
},
),
),
),
);
}
}
class _FavoriteViews extends StatelessWidget {
const _FavoriteViews({
required this.favoriteViews,
});
final List<SectionViewPB> favoriteViews;
@override
Widget build(BuildContext context) {
final borderColor = Theme.of(context).isLightMode
? const Color(0xFFE9E9EC)
: const Color(0x1AFFFFFF);
return Scrollbar(
child: ListView.separated(
key: const PageStorageKey('favorite_views_page_storage_key'),
padding: EdgeInsets.only(
left: HomeSpaceViewSizes.mHorizontalPadding,
right: HomeSpaceViewSizes.mHorizontalPadding,
bottom: HomeSpaceViewSizes.mVerticalPadding +
MediaQuery.of(context).padding.bottom,
),
itemBuilder: (context, index) {
final view = favoriteViews[index];
return Container(
padding: const EdgeInsets.symmetric(vertical: 24.0),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: borderColor,
width: 0.5,
),
),
),
child: MobileViewPage(
key: ValueKey(view.item.id),
view: view.item,
timestamp: view.timestamp,
type: MobilePageCardType.favorite,
),
);
},
separatorBuilder: (context, index) => const HSpace(8),
itemCount: favoriteViews.length,
),
);
}
}
@@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart';
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder_header.dart';
@@ -7,6 +5,7 @@ import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileFavoriteFolder extends StatelessWidget {
@@ -28,7 +27,7 @@ class MobileFavoriteFolder extends StatelessWidget {
}
return BlocProvider<FolderBloc>(
create: (context) => FolderBloc(type: FolderCategoryType.favorite)
create: (context) => FolderBloc(type: FolderSpaceType.favorite)
..add(
const FolderEvent.initial(),
),
@@ -55,21 +54,25 @@ class MobileFavoriteFolder extends StatelessWidget {
...views.map(
(view) => MobileViewItem(
key: ValueKey(
'${FolderCategoryType.favorite.name} ${view.id}',
'${FolderSpaceType.favorite.name} ${view.id}',
),
categoryType: FolderCategoryType.favorite,
spaceType: FolderSpaceType.favorite,
isDraggable: false,
isFirstChild: view.id == views.first.id,
isFeedback: false,
view: view,
level: 0,
onSelected: context.pushView,
endActionPane: (context) => buildEndActionPane(context, [
view.isFavorite
? MobilePaneActionType.removeFromFavorites
: MobilePaneActionType.addToFavorites,
MobilePaneActionType.more,
]),
endActionPane: (context) => buildEndActionPane(
context,
[
view.isFavorite
? MobilePaneActionType.removeFromFavorites
: MobilePaneActionType.addToFavorites,
MobilePaneActionType.more,
],
spaceType: FolderSpaceType.favorite,
),
),
),
],
@@ -0,0 +1,47 @@
import 'package:appflowy/mobile/presentation/home/mobile_folders.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileHomeSpace extends StatefulWidget {
const MobileHomeSpace({super.key, required this.userProfile});
final UserProfilePB userProfile;
@override
State<MobileHomeSpace> createState() => _MobileHomeSpaceState();
}
class _MobileHomeSpaceState extends State<MobileHomeSpace>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
final workspaceId =
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
'';
return Scrollbar(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.only(
left: HomeSpaceViewSizes.mHorizontalPadding,
right: HomeSpaceViewSizes.mHorizontalPadding,
top: HomeSpaceViewSizes.mVerticalPadding,
bottom: HomeSpaceViewSizes.mVerticalPadding +
MediaQuery.of(context).padding.bottom,
),
child: MobileFolders(
user: widget.userProfile,
workspaceId: workspaceId,
showFavorite: false,
),
),
),
);
}
}
@@ -1,9 +1,13 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/home.dart';
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder.dart';
import 'package:appflowy/mobile/presentation/home/space/mobile_space.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
@@ -11,6 +15,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:go_router/go_router.dart';
// Contains Public And Private Sections
class MobileFolders extends StatelessWidget {
@@ -31,16 +36,15 @@ class MobileFolders extends StatelessWidget {
providers: [
BlocProvider(
create: (_) => SidebarSectionsBloc()
..add(
SidebarSectionsEvent.initial(
user,
workspaceId,
),
),
..add(SidebarSectionsEvent.initial(user, workspaceId)),
),
BlocProvider(
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
),
BlocProvider(
create: (_) => SpaceBloc()
..add(SpaceEvent.initial(user, workspaceId, openFirstPage: false)),
),
],
child: BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
listener: (context, state) {
@@ -50,50 +54,111 @@ class MobileFolders extends StatelessWidget {
state.currentWorkspace?.workspaceId ?? workspaceId,
),
);
context.read<SpaceBloc>().add(
SpaceEvent.reset(
user,
state.currentWorkspace?.workspaceId ?? workspaceId,
),
);
},
child: BlocConsumer<SidebarSectionsBloc, SidebarSectionsState>(
listenWhen: (p, c) =>
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
listener: (context, state) {
final lastCreatedRootView = state.lastCreatedRootView;
if (lastCreatedRootView != null) {
context.pushView(lastCreatedRootView);
}
},
builder: (context, state) {
final isCollaborativeWorkspace =
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
return SlidableAutoCloseBehavior(
child: Column(
children: [
...isCollaborativeWorkspace
? [
MobileSectionFolder(
title: LocaleKeys.sideBar_workspace.tr(),
categoryType: FolderCategoryType.public,
views: state.section.publicViews,
),
const VSpace(8.0),
MobileSectionFolder(
title: LocaleKeys.sideBar_private.tr(),
categoryType: FolderCategoryType.private,
views: state.section.privateViews,
),
]
: [
MobileSectionFolder(
title: LocaleKeys.sideBar_personal.tr(),
categoryType: FolderCategoryType.public,
views: state.section.publicViews,
),
],
const VSpace(8.0),
],
),
);
},
child: MultiBlocListener(
listeners: [
BlocListener<SpaceBloc, SpaceState>(
listenWhen: (p, c) =>
p.lastCreatedPage?.id != c.lastCreatedPage?.id,
listener: (context, state) {
final lastCreatedPage = state.lastCreatedPage;
if (lastCreatedPage != null) {
context.pushView(lastCreatedPage);
}
},
),
BlocListener<SidebarSectionsBloc, SidebarSectionsState>(
listenWhen: (p, c) =>
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
listener: (context, state) {
final lastCreatedPage = state.lastCreatedRootView;
if (lastCreatedPage != null) {
context.pushView(lastCreatedPage);
}
},
),
],
child: BlocBuilder<SidebarSectionsBloc, SidebarSectionsState>(
builder: (context, state) {
return SlidableAutoCloseBehavior(
child: Column(
children: [
..._buildSpaceOrSection(context, state),
const VSpace(4.0),
const _TrashButton(),
],
),
);
},
),
),
),
);
}
List<Widget> _buildSpaceOrSection(
BuildContext context,
SidebarSectionsState state,
) {
if (context.watch<SpaceBloc>().state.spaces.isNotEmpty) {
return [
const MobileSpace(),
];
}
if (context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn) {
return [
MobileSectionFolder(
title: LocaleKeys.sideBar_workspace.tr(),
spaceType: FolderSpaceType.public,
views: state.section.publicViews,
),
const VSpace(8.0),
MobileSectionFolder(
title: LocaleKeys.sideBar_private.tr(),
spaceType: FolderSpaceType.private,
views: state.section.privateViews,
),
];
}
return [
MobileSectionFolder(
title: LocaleKeys.sideBar_personal.tr(),
spaceType: FolderSpaceType.public,
views: state.section.publicViews,
),
];
}
}
class _TrashButton extends StatelessWidget {
const _TrashButton();
@override
Widget build(BuildContext context) {
return SizedBox(
height: 52,
child: FlowyButton(
expand: true,
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 2.0),
leftIcon: const FlowySvg(
FlowySvgs.m_delete_s,
),
leftIconSize: const Size.square(18),
iconPadding: 10.0,
text: FlowyText.regular(
LocaleKeys.trash_text.tr(),
fontSize: 16.0,
),
onTap: () => context.push(MobileHomeTrashPage.routeName),
),
);
}
}
@@ -1,23 +1,24 @@
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/home/home.dart';
import 'package:appflowy/mobile/presentation/home/mobile_folders.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
import 'package:appflowy/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart';
import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart';
import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
class MobileHomeScreen extends StatelessWidget {
@@ -58,6 +59,7 @@ class MobileHomeScreen extends StatelessWidget {
return Scaffold(
body: SafeArea(
bottom: false,
child: Provider.value(
value: userProfile,
child: MobileHomePage(
@@ -72,7 +74,7 @@ class MobileHomeScreen extends StatelessWidget {
}
}
class MobileHomePage extends StatelessWidget {
class MobileHomePage extends StatefulWidget {
const MobileHomePage({
super.key,
required this.userProfile,
@@ -82,68 +84,70 @@ class MobileHomePage extends StatelessWidget {
final UserProfilePB userProfile;
final WorkspaceSettingPB workspaceSetting;
@override
State<MobileHomePage> createState() => _MobileHomePageState();
}
class _MobileHomePageState extends State<MobileHomePage> {
@override
void initState() {
super.initState();
getIt<MenuSharedState>().addLatestViewListener(_onLatestViewChange);
}
@override
void dispose() {
getIt<MenuSharedState>().removeLatestViewListener(_onLatestViewChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => UserWorkspaceBloc(userProfile: userProfile)
..add(
const UserWorkspaceEvent.initial(),
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => UserWorkspaceBloc(userProfile: widget.userProfile)
..add(const UserWorkspaceEvent.initial()),
),
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
BlocProvider(
create: (context) =>
FavoriteBloc()..add(const FavoriteEvent.initial()),
),
BlocProvider.value(
value: getIt<ReminderBloc>()..add(const ReminderEvent.started()),
),
],
child: BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>(
buildWhen: (previous, current) =>
previous.currentWorkspace?.workspaceId !=
current.currentWorkspace?.workspaceId,
listener: (context, state) => getIt<CachedRecentService>().reset(),
builder: (context, state) {
if (state.currentWorkspace == null) {
return const SizedBox.shrink();
}
return Column(
children: [
// Header
Padding(
padding: EdgeInsets.only(
left: 16,
right: 16,
left: HomeSpaceViewSizes.mHorizontalPadding,
right: 8.0,
top: Platform.isAndroid ? 8.0 : 0.0,
),
child: MobileHomePageHeader(
userProfile: userProfile,
userProfile: widget.userProfile,
),
),
const Divider(),
// Folder
Expanded(
child: Scrollbar(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Recent files
const MobileRecentFolder(),
// Folders
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: MobileFolders(
user: userProfile,
workspaceId:
state.currentWorkspace?.workspaceId ??
workspaceSetting.workspaceId,
showFavorite: false,
),
),
const SizedBox(height: 8),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 24),
child: _TrashButton(),
),
],
),
),
child: BlocProvider(
create: (context) =>
SpaceOrderBloc()..add(const SpaceOrderEvent.initial()),
child: MobileSpaceTab(
userProfile: widget.userProfile,
),
),
),
@@ -153,26 +157,12 @@ class MobileHomePage extends StatelessWidget {
),
);
}
}
class _TrashButton extends StatelessWidget {
const _TrashButton();
@override
Widget build(BuildContext context) {
return FlowyButton(
expand: true,
margin: const EdgeInsets.symmetric(vertical: 8),
leftIcon: FlowySvg(
FlowySvgs.m_delete_m,
color: Theme.of(context).colorScheme.onSurface,
),
leftIconSize: const Size.square(24),
text: FlowyText.medium(
LocaleKeys.trash_text.tr(),
fontSize: 18.0,
),
onTap: () => context.push(MobileHomeTrashPage.routeName),
);
void _onLatestViewChange() async {
final id = getIt<MenuSharedState>().latestOpenView?.id;
if (id == null) {
return;
}
await FolderEventSetLatestView(ViewIdPB(value: id)).send();
}
}
@@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
@@ -15,6 +13,7 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sid
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -36,7 +35,7 @@ class MobileHomePageHeader extends StatelessWidget {
final isCollaborativeWorkspace =
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 52),
constraints: const BoxConstraints(minHeight: 56),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -45,11 +44,14 @@ class MobileHomePageHeader extends StatelessWidget {
? _MobileWorkspace(userProfile: userProfile)
: _MobileUser(userProfile: userProfile),
),
IconButton(
onPressed: () => context.push(
GestureDetector(
onTap: () => context.push(
MobileHomeSettingPage.routeName,
),
icon: const FlowySvg(FlowySvgs.m_setting_m),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: FlowySvg(FlowySvgs.m_setting_m),
),
),
],
),
@@ -120,13 +122,14 @@ class _MobileWorkspace extends StatelessWidget {
},
child: Row(
children: [
const HSpace(2.0),
SizedBox.square(
dimension: 34.0,
dimension: currentWorkspace.icon.isNotEmpty ? 34.0 : 26.0,
child: WorkspaceIcon(
workspace: currentWorkspace,
iconSize: 26,
fontSize: 16.0,
enableEdit: false,
alignment: Alignment.centerLeft,
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
UserWorkspaceEvent.updateWorkspaceIcon(
currentWorkspace.workspaceId,
@@ -135,32 +138,13 @@ class _MobileWorkspace extends StatelessWidget {
),
),
),
const HSpace(8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
FlowyText.medium(
currentWorkspace.name,
fontSize: 16.0,
overflow: TextOverflow.ellipsis,
),
const HSpace(4.0),
const FlowySvg(FlowySvgs.list_dropdown_s),
],
),
FlowyText.medium(
userProfile.email.isNotEmpty
? userProfile.email
: userProfile.name,
overflow: TextOverflow.ellipsis,
fontSize: 12,
color: Theme.of(context).colorScheme.onSurface,
),
],
),
currentWorkspace.icon.isNotEmpty
? const HSpace(2)
: const HSpace(8),
FlowyText.semibold(
currentWorkspace.name,
fontSize: 16.0,
overflow: TextOverflow.ellipsis,
),
],
),
@@ -177,7 +161,9 @@ class _MobileWorkspace extends StatelessWidget {
showDivider: false,
showHeader: true,
showDragHandle: true,
showCloseButton: true,
title: LocaleKeys.workspace_menuTitle.tr(),
backgroundColor: Theme.of(context).colorScheme.surface,
builder: (_) {
return BlocProvider.value(
value: context.read<UserWorkspaceBloc>(),
@@ -178,7 +178,7 @@ class _DeletedFilesListView extends StatelessWidget {
title: Text(
deletedFile.name,
style: theme.textTheme.labelMedium
?.copyWith(color: theme.colorScheme.onBackground),
?.copyWith(color: theme.colorScheme.onSurface),
),
horizontalTitleGap: 0,
tileColor: theme.colorScheme.onSurface.withOpacity(0.1),
@@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
@@ -9,7 +7,9 @@ import 'package:appflowy/workspace/application/recent/prelude.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -38,7 +38,8 @@ class _MobileRecentFolderState extends State<MobileRecentFolder> {
builder: (context, state) {
final ids = <String>{};
List<ViewPB> recentViews = state.views.reversed.toList();
List<ViewPB> recentViews =
state.views.reversed.map((e) => e.item).toList();
recentViews.retainWhere((element) => ids.add(element.id));
// only keep the first 20 items.
@@ -91,7 +92,7 @@ class _RecentViews extends StatelessWidget {
context,
showDivider: false,
showDragHandle: true,
backgroundColor: Theme.of(context).colorScheme.background,
backgroundColor: AFThemeExtension.of(context).background,
builder: (_) {
return Column(
children: [
@@ -0,0 +1,106 @@
import 'package:appflowy/mobile/presentation/home/shared/empty_placeholder.dart';
import 'package:appflowy/mobile/presentation/home/shared/mobile_page_card.dart';
import 'package:appflowy/util/theme_extension.dart';
import 'package:appflowy/workspace/application/recent/prelude.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class MobileRecentSpace extends StatefulWidget {
const MobileRecentSpace({super.key});
@override
State<MobileRecentSpace> createState() => _MobileRecentSpaceState();
}
class _MobileRecentSpaceState extends State<MobileRecentSpace>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return BlocProvider(
create: (context) =>
RecentViewsBloc()..add(const RecentViewsEvent.initial()),
child: BlocBuilder<RecentViewsBloc, RecentViewsState>(
builder: (context, state) {
if (state.isLoading) {
return const SizedBox.shrink();
}
final recentViews = _filterRecentViews(state.views);
if (recentViews.isEmpty) {
return const Center(
child: EmptySpacePlaceholder(type: MobilePageCardType.recent),
);
}
return _RecentViews(recentViews: recentViews);
},
),
);
}
List<SectionViewPB> _filterRecentViews(List<SectionViewPB> recentViews) {
final ids = <String>{};
final filteredRecentViews = recentViews.reversed.toList();
filteredRecentViews.retainWhere((e) => ids.add(e.item.id));
return filteredRecentViews;
}
}
class _RecentViews extends StatelessWidget {
const _RecentViews({
required this.recentViews,
});
final List<SectionViewPB> recentViews;
@override
Widget build(BuildContext context) {
final borderColor = Theme.of(context).isLightMode
? const Color(0xFFE9E9EC)
: const Color(0x1AFFFFFF);
return SlidableAutoCloseBehavior(
child: Scrollbar(
child: ListView.separated(
key: const PageStorageKey('recent_views_page_storage_key'),
padding: EdgeInsets.only(
left: HomeSpaceViewSizes.mHorizontalPadding,
right: HomeSpaceViewSizes.mHorizontalPadding,
bottom: HomeSpaceViewSizes.mVerticalPadding +
MediaQuery.of(context).padding.bottom,
),
itemBuilder: (context, index) {
final sectionView = recentViews[index];
return Container(
padding: const EdgeInsets.symmetric(vertical: 24.0),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: borderColor,
width: 0.5,
),
),
),
child: MobileViewPage(
key: ValueKey(sectionView.item.id),
view: sectionView.item,
timestamp: sectionView.timestamp,
type: MobilePageCardType.recent,
),
);
},
separatorBuilder: (context, index) => const HSpace(8),
itemCount: recentViews.length,
),
),
);
}
}
@@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart';
@@ -8,9 +6,10 @@ import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileSectionFolder extends StatelessWidget {
@@ -18,17 +17,17 @@ class MobileSectionFolder extends StatelessWidget {
super.key,
required this.title,
required this.views,
required this.categoryType,
required this.spaceType,
});
final String title;
final List<ViewPB> views;
final FolderCategoryType categoryType;
final FolderSpaceType spaceType;
@override
Widget build(BuildContext context) {
return BlocProvider<FolderBloc>(
create: (context) => FolderBloc(type: categoryType)
create: (context) => FolderBloc(type: spaceType)
..add(
const FolderEvent.initial(),
),
@@ -36,52 +35,54 @@ class MobileSectionFolder extends StatelessWidget {
builder: (context, state) {
return Column(
children: [
MobileSectionFolderHeader(
title: title,
isExpanded: context.read<FolderBloc>().state.isExpanded,
onPressed: () => context
.read<FolderBloc>()
.add(const FolderEvent.expandOrUnExpand()),
onAdded: () {
context.read<SidebarSectionsBloc>().add(
SidebarSectionsEvent.createRootViewInSection(
name:
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
index: 0,
viewSection: categoryType.toViewSectionPB,
),
);
context.read<FolderBloc>().add(
const FolderEvent.expandOrUnExpand(isExpanded: true),
);
},
),
const VSpace(8.0),
const Divider(
height: 1,
SizedBox(
height: HomeSpaceViewSizes.mViewHeight,
child: MobileSectionFolderHeader(
title: title,
isExpanded: context.read<FolderBloc>().state.isExpanded,
onPressed: () => context
.read<FolderBloc>()
.add(const FolderEvent.expandOrUnExpand()),
onAdded: () {
context.read<SidebarSectionsBloc>().add(
SidebarSectionsEvent.createRootViewInSection(
name: LocaleKeys.menuAppHeader_defaultNewPageName
.tr(),
index: 0,
viewSection: spaceType.toViewSectionPB,
),
);
context.read<FolderBloc>().add(
const FolderEvent.expandOrUnExpand(isExpanded: true),
);
},
),
),
if (state.isExpanded)
...views.map(
(view) => MobileViewItem(
key: ValueKey(
'${FolderCategoryType.private.name} ${view.id}',
'${FolderSpaceType.private.name} ${view.id}',
),
categoryType: categoryType,
spaceType: spaceType,
isFirstChild: view.id == views.first.id,
view: view,
level: 0,
leftPadding: 16,
leftPadding: HomeSpaceViewSizes.leftPadding,
isFeedback: false,
onSelected: context.pushView,
endActionPane: (context) {
final view = context.read<ViewBloc>().state.view;
return buildEndActionPane(context, [
MobilePaneActionType.delete,
view.isFavorite
? MobilePaneActionType.removeFromFavorites
: MobilePaneActionType.addToFavorites,
MobilePaneActionType.more,
]);
return buildEndActionPane(
context,
[
MobilePaneActionType.more,
if (view.layout == ViewLayoutPB.Document)
MobilePaneActionType.add,
],
spaceType: spaceType,
needSpace: false,
);
},
),
),
@@ -1,4 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@@ -29,24 +30,23 @@ class _MobileSectionFolderHeaderState extends State<MobileSectionFolderHeader> {
@override
Widget build(BuildContext context) {
const iconSize = 32.0;
return Row(
children: [
Expanded(
child: FlowyButton(
text: FlowyText.semibold(
text: FlowyText.medium(
widget.title,
fontSize: 20.0,
fontSize: 16.0,
),
margin: const EdgeInsets.symmetric(vertical: 8),
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 2.0),
expandText: false,
iconPadding: 2,
mainAxisAlignment: MainAxisAlignment.start,
rightIcon: AnimatedRotation(
duration: const Duration(milliseconds: 200),
turns: _turns,
child: const Icon(
Icons.keyboard_arrow_down_rounded,
color: Colors.grey,
child: const FlowySvg(
FlowySvgs.m_spaces_expand_s,
),
),
onTap: () {
@@ -60,12 +60,10 @@ class _MobileSectionFolderHeaderState extends State<MobileSectionFolderHeader> {
FlowyIconButton(
key: mobileCreateNewPageButtonKey,
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
iconPadding: const EdgeInsets.all(2),
height: iconSize,
width: iconSize,
height: HomeSpaceViewSizes.mViewButtonDimension,
width: HomeSpaceViewSizes.mViewButtonDimension,
icon: const FlowySvg(
FlowySvgs.add_s,
size: Size.square(iconSize),
FlowySvgs.m_space_add_s,
),
onPressed: widget.onAdded,
),
@@ -0,0 +1,55 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/home/shared/mobile_page_card.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class EmptySpacePlaceholder extends StatelessWidget {
const EmptySpacePlaceholder({super.key, required this.type});
final MobilePageCardType type;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 48.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const FlowySvg(
FlowySvgs.m_empty_page_xl,
),
const VSpace(16.0),
FlowyText.medium(
_emptyPageText,
fontSize: 18.0,
textAlign: TextAlign.center,
),
const VSpace(8.0),
FlowyText.regular(
_emptyPageSubText,
fontSize: 17.0,
maxLines: 10,
textAlign: TextAlign.center,
lineHeight: 1.3,
color: Theme.of(context).hintColor,
),
],
),
);
}
String get _emptyPageText => switch (type) {
MobilePageCardType.recent => LocaleKeys.sideBar_emptyRecent.tr(),
MobilePageCardType.favorite => LocaleKeys.sideBar_emptyFavorite.tr(),
};
String get _emptyPageSubText => switch (type) {
MobilePageCardType.recent =>
LocaleKeys.sideBar_emptyRecentDescription.tr(),
MobilePageCardType.favorite =>
LocaleKeys.sideBar_emptyFavoriteDescription.tr(),
};
}
@@ -0,0 +1,406 @@
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/application/recent/recent_view_bloc.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/shared/flowy_gradient_colors.dart';
import 'package:appflowy/util/string_extension.dart';
import 'package:appflowy/util/theme_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
import 'package:appflowy/workspace/application/settings/date_time/time_format_ext.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'package:string_validator/string_validator.dart';
import 'package:time/time.dart';
enum MobilePageCardType {
recent,
favorite;
String get lastOperationHintText => switch (this) {
MobilePageCardType.recent => LocaleKeys.sideBar_lastViewed.tr(),
MobilePageCardType.favorite => LocaleKeys.sideBar_favoriteAt.tr(),
};
}
class MobileViewPage extends StatelessWidget {
const MobileViewPage({
super.key,
required this.view,
this.timestamp,
required this.type,
});
final ViewPB view;
final Int64? timestamp;
final MobilePageCardType type;
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<ViewBloc>(
create: (context) => ViewBloc(view: view, shouldLoadChildViews: false)
..add(const ViewEvent.initial()),
),
BlocProvider(
create: (context) =>
RecentViewBloc(view: view)..add(const RecentViewEvent.initial()),
),
],
child: BlocBuilder<RecentViewBloc, RecentViewState>(
builder: (context, state) {
return Slidable(
endActionPane: buildEndActionPane(
context,
[
MobilePaneActionType.more,
context.watch<ViewBloc>().state.view.isFavorite
? MobilePaneActionType.removeFromFavorites
: MobilePaneActionType.addToFavorites,
],
cardType: type,
),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapUp: (_) => context.pushView(view),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: _buildDescription(context, state)),
const HSpace(20.0),
SizedBox(
width: 84,
height: 60,
child: _buildCover(context, state),
),
],
),
),
);
},
),
);
}
Widget _buildDescription(BuildContext context, RecentViewState state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// page icon & page title
_buildTitle(context, state),
const VSpace(12.0),
// author & last viewed
_buildNameAndLastViewed(context, state),
],
);
}
Widget _buildNameAndLastViewed(BuildContext context, RecentViewState state) {
final supportAvatar = isURL(state.icon);
if (!supportAvatar) {
return _buildLastViewed(context);
}
return Row(
children: [
_buildAvatar(context, state),
Flexible(child: _buildAuthor(context, state)),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 3.0),
child: FlowySvg(FlowySvgs.dot_s),
),
_buildLastViewed(context),
],
);
}
Widget _buildAvatar(BuildContext context, RecentViewState state) {
final userProfile = Provider.of<UserProfilePB?>(context);
final iconUrl = userProfile?.iconUrl;
if (iconUrl == null ||
iconUrl.isEmpty ||
view.createdBy != userProfile?.id) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.only(top: 2, bottom: 2, right: 8),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: SizedBox.square(
dimension: 16.0,
child: FlowyNetworkImage(
url: iconUrl,
),
),
),
);
}
Widget _buildCover(BuildContext context, RecentViewState state) {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: _ViewCover(
layout: view.layout,
coverTypeV1: state.coverTypeV1,
coverTypeV2: state.coverTypeV2,
value: state.coverValue,
),
);
}
Widget _buildTitle(BuildContext context, RecentViewState state) {
final name = state.name;
final icon = state.icon;
final fontFamily = Platform.isAndroid || Platform.isLinux
? GoogleFonts.notoColorEmoji().fontFamily
: null;
return RichText(
maxLines: 3,
overflow: TextOverflow.ellipsis,
text: TextSpan(
children: [
TextSpan(
text: icon,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 17.0,
fontWeight: FontWeight.w600,
fontFamily: fontFamily,
),
),
if (icon.isNotEmpty) const WidgetSpan(child: HSpace(2.0)),
TextSpan(
text: name,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16.0,
fontWeight: FontWeight.w600,
height: 1.3,
),
),
],
),
);
}
Widget _buildAuthor(BuildContext context, RecentViewState state) {
return FlowyText.regular(
// view.createdBy.toString(),
'Lucas',
fontSize: 12.0,
color: Theme.of(context).hintColor,
overflow: TextOverflow.ellipsis,
);
}
Widget _buildLastViewed(BuildContext context) {
final textColor = Theme.of(context).isLightMode
? const Color(0xFF171717)
: Colors.white.withOpacity(0.45);
if (timestamp == null) {
return const SizedBox.shrink();
}
final date = _formatTimestamp(
context,
timestamp!.toInt() * 1000,
);
return FlowyText.regular(
date,
fontSize: 13.0,
color: textColor,
);
}
String _formatTimestamp(BuildContext context, int timestamp) {
final now = DateTime.now();
final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
final difference = now.difference(dateTime);
final String date;
final dateFormate =
context.read<AppearanceSettingsCubit>().state.dateFormat;
final timeFormate =
context.read<AppearanceSettingsCubit>().state.timeFormat;
if (difference.inMinutes < 1) {
date = LocaleKeys.sideBar_justNow.tr();
} else if (difference.inHours < 1 && dateTime.isToday) {
// Less than 1 hour
date = LocaleKeys.sideBar_minutesAgo
.tr(namedArgs: {'count': difference.inMinutes.toString()});
} else if (difference.inHours >= 1 && dateTime.isToday) {
// in same day
date = timeFormate.formatTime(dateTime);
} else {
date = dateFormate.formatDate(dateTime, false);
}
if (difference.inHours >= 1) {
return '${type.lastOperationHintText} $date';
}
return date;
}
}
class _ViewCover extends StatelessWidget {
const _ViewCover({
required this.layout,
required this.coverTypeV1,
this.coverTypeV2,
this.value,
});
final ViewLayoutPB layout;
final CoverType coverTypeV1;
final PageStyleCoverImageType? coverTypeV2;
final String? value;
@override
Widget build(BuildContext context) {
final placeholder = _buildPlaceholder(context);
final value = this.value;
if (value == null) {
return placeholder;
}
if (coverTypeV2 != null) {
return _buildCoverV2(context, value, placeholder);
}
return _buildCoverV1(context, value, placeholder);
}
Widget _buildPlaceholder(BuildContext context) {
final isLightMode = Theme.of(context).isLightMode;
final (svg, color) = switch (layout) {
ViewLayoutPB.Document => (
FlowySvgs.m_document_thumbnail_m,
isLightMode ? const Color(0xCCEDFBFF) : const Color(0x33658B90)
),
ViewLayoutPB.Grid => (
FlowySvgs.m_grid_thumbnail_m,
isLightMode ? const Color(0xFFF5F4FF) : const Color(0x338B80AD)
),
ViewLayoutPB.Board => (
FlowySvgs.m_board_thumbnail_m,
isLightMode ? const Color(0x7FE0FDD9) : const Color(0x3372936B),
),
ViewLayoutPB.Calendar => (
FlowySvgs.m_calendar_thumbnail_m,
isLightMode ? const Color(0xFFFFF7F0) : const Color(0x33A68B77)
),
ViewLayoutPB.Chat => (
FlowySvgs.m_chat_thumbnail_m,
isLightMode ? const Color(0x66FFE6FD) : const Color(0x33987195)
),
_ => (
FlowySvgs.m_document_thumbnail_m,
isLightMode ? Colors.black : Colors.white
)
};
return ColoredBox(
color: color,
child: Center(
child: FlowySvg(
svg,
blendMode: null,
),
),
);
}
Widget _buildCoverV2(BuildContext context, String value, Widget placeholder) {
final type = coverTypeV2;
if (type == null) {
return placeholder;
}
if (type == PageStyleCoverImageType.customImage ||
type == PageStyleCoverImageType.unsplashImage) {
final userProfilePB = Provider.of<UserProfilePB?>(context);
return FlowyNetworkImage(
url: value,
userProfilePB: userProfilePB,
);
}
if (type == PageStyleCoverImageType.builtInImage) {
return Image.asset(
PageStyleCoverImageType.builtInImagePath(value),
fit: BoxFit.cover,
);
}
if (type == PageStyleCoverImageType.pureColor) {
final color = value.coverColor(context);
if (color != null) {
return ColoredBox(
color: color,
);
}
}
if (type == PageStyleCoverImageType.gradientColor) {
return Container(
decoration: BoxDecoration(
gradient: FlowyGradientColor.fromId(value).linear,
),
);
}
if (type == PageStyleCoverImageType.localImage) {
return Image.file(
File(value),
fit: BoxFit.cover,
);
}
return placeholder;
}
Widget _buildCoverV1(BuildContext context, String value, Widget placeholder) {
switch (coverTypeV1) {
case CoverType.file:
if (isURL(value)) {
final userProfilePB = Provider.of<UserProfilePB?>(context);
return FlowyNetworkImage(
url: value,
userProfilePB: userProfilePB,
);
}
final imageFile = File(value);
if (!imageFile.existsSync()) {
return placeholder;
}
return Image.file(
imageFile,
);
case CoverType.asset:
return Image.asset(
value,
fit: BoxFit.cover,
);
case CoverType.color:
final color = value.tryToColor() ?? Colors.white;
return Container(
color: color,
);
case CoverType.none:
return placeholder;
}
}
}
@@ -0,0 +1,162 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/home/space/mobile_space_header.dart';
import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileSpace extends StatefulWidget {
const MobileSpace({super.key});
@override
State<MobileSpace> createState() => _MobileSpaceState();
}
class _MobileSpaceState extends State<MobileSpace> {
@override
void initState() {
super.initState();
createNewPageNotifier.addListener(_createNewPage);
}
@override
void dispose() {
createNewPageNotifier.removeListener(_createNewPage);
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceBloc, SpaceState>(
builder: (context, state) {
if (state.spaces.isEmpty) {
return const SizedBox.shrink();
}
final currentSpace = state.currentSpace ?? state.spaces.first;
return Column(
children: [
MobileSpaceHeader(
isExpanded: state.isExpanded,
space: currentSpace,
onAdded: () {
context.read<SpaceBloc>().add(
SpaceEvent.createPage(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout: ViewLayoutPB.Document,
index: 0,
),
);
context.read<SpaceBloc>().add(
SpaceEvent.expand(currentSpace, true),
);
},
onPressed: () => _showSpaceMenu(context),
),
_Pages(
key: ValueKey(currentSpace.id),
space: currentSpace,
),
],
);
},
);
}
void _showSpaceMenu(BuildContext context) {
showMobileBottomSheet(
context,
showDivider: false,
showHeader: true,
showDragHandle: true,
showCloseButton: true,
showDoneButton: true,
title: LocaleKeys.space_title.tr(),
backgroundColor: Theme.of(context).colorScheme.surface,
builder: (_) {
return BlocProvider.value(
value: context.read<SpaceBloc>(),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: MobileSpaceMenu(),
),
);
},
);
}
void _createNewPage() {
context.read<SpaceBloc>().add(
SpaceEvent.createPage(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout: ViewLayoutPB.Document,
),
);
}
}
class _Pages extends StatelessWidget {
const _Pages({
super.key,
required this.space,
});
final ViewPB space;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
ViewBloc(view: space)..add(const ViewEvent.initial()),
child: BlocBuilder<ViewBloc, ViewState>(
builder: (context, state) {
final spaceType = space.spacePermission == SpacePermission.publicToAll
? FolderSpaceType.public
: FolderSpaceType.private;
return Column(
children: state.view.childViews
.map(
(view) => MobileViewItem(
key: ValueKey(
'${space.id} ${view.id}',
),
spaceType: spaceType,
isFirstChild: view.id == state.view.childViews.first.id,
view: view,
level: 0,
leftPadding: HomeSpaceViewSizes.leftPadding,
isFeedback: false,
onSelected: context.pushView,
endActionPane: (context) {
final view = context.read<ViewBloc>().state.view;
return buildEndActionPane(
context,
[
MobilePaneActionType.more,
if (view.layout == ViewLayoutPB.Document)
MobilePaneActionType.add,
],
spaceType: spaceType,
needSpace: false,
);
},
),
)
.toList(),
);
},
),
);
}
}
@@ -0,0 +1,132 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@visibleForTesting
const Key mobileCreateNewPageButtonKey = Key('mobileCreateNewPageButtonKey');
class MobileSpaceHeader extends StatelessWidget {
const MobileSpaceHeader({
super.key,
required this.space,
required this.onPressed,
required this.onAdded,
required this.isExpanded,
});
final ViewPB space;
final VoidCallback onPressed;
final VoidCallback onAdded;
final bool isExpanded;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: onPressed,
child: SizedBox(
height: 48,
child: Row(
children: [
SpaceIcon(
dimension: 24,
space: space,
cornerRadius: 6.0,
),
const HSpace(8),
FlowyText.medium(
space.name,
lineHeight: 1.15,
fontSize: 16.0,
),
const HSpace(4.0),
const FlowySvg(
FlowySvgs.workspace_drop_down_menu_show_s,
),
const Spacer(),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: onAdded,
child: const FlowySvg(
FlowySvgs.m_space_add_s,
),
),
],
),
),
);
}
// Future<void> _onAction(SpaceMoreActionType type, dynamic data) async {
// switch (type) {
// case SpaceMoreActionType.rename:
// await _showRenameDialog();
// break;
// case SpaceMoreActionType.changeIcon:
// final (String icon, String iconColor) = data;
// context.read<SpaceBloc>().add(SpaceEvent.changeIcon(icon, iconColor));
// break;
// case SpaceMoreActionType.manage:
// _showManageSpaceDialog(context);
// break;
// case SpaceMoreActionType.addNewSpace:
// break;
// case SpaceMoreActionType.collapseAllPages:
// break;
// case SpaceMoreActionType.delete:
// _showDeleteSpaceDialog(context);
// break;
// case SpaceMoreActionType.divider:
// break;
// }
// }
// Future<void> _showRenameDialog() async {
// await NavigatorTextFieldDialog(
// title: LocaleKeys.space_rename.tr(),
// value: space.name,
// autoSelectAllText: true,
// onConfirm: (name, _) {
// context.read<SpaceBloc>().add(SpaceEvent.rename(space, name));
// },
// ).show(context);
// }
// void _showManageSpaceDialog(BuildContext context) {
// final spaceBloc = context.read<SpaceBloc>();
// showDialog(
// context: context,
// builder: (_) {
// return Dialog(
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12.0),
// ),
// child: BlocProvider.value(
// value: spaceBloc,
// child: const ManageSpacePopup(),
// ),
// );
// },
// );
// }
// void _showDeleteSpaceDialog(BuildContext context) {
// final spaceBloc = context.read<SpaceBloc>();
// showDialog(
// context: context,
// builder: (_) {
// return Dialog(
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12.0),
// ),
// child: BlocProvider.value(
// value: spaceBloc,
// child: const SizedBox(width: 440, child: DeleteSpacePopup()),
// ),
// );
// },
// );
// }
}
@@ -0,0 +1,128 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileSpaceMenu extends StatelessWidget {
const MobileSpaceMenu({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceBloc, SpaceState>(
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const VSpace(4.0),
for (final space in state.spaces)
SizedBox(
height: 52,
child: _SidebarSpaceMenuItem(
space: space,
isSelected: state.currentSpace?.id == space.id,
),
),
// const Padding(
// padding: EdgeInsets.symmetric(vertical: 8.0),
// child: Divider(
// height: 0.5,
// ),
// ),
// const SizedBox(
// height: 52,
// child: _CreateSpaceButton(),
// ),
],
);
},
);
}
}
class _SidebarSpaceMenuItem extends StatelessWidget {
const _SidebarSpaceMenuItem({
required this.space,
required this.isSelected,
});
final ViewPB space;
final bool isSelected;
@override
Widget build(BuildContext context) {
return FlowyButton(
text: Row(
children: [
FlowyText.medium(
space.name,
),
const HSpace(6.0),
if (space.spacePermission == SpacePermission.private)
const FlowySvg(
FlowySvgs.space_lock_s,
size: Size.square(12),
),
],
),
iconPadding: 10,
leftIcon: SpaceIcon(
dimension: 24,
space: space,
cornerRadius: 6.0,
),
leftIconSize: const Size.square(20),
rightIcon: isSelected
? const FlowySvg(
FlowySvgs.workspace_selected_s,
blendMode: null,
)
: null,
onTap: () {
context.read<SpaceBloc>().add(SpaceEvent.open(space));
Navigator.of(context).pop();
},
);
}
}
// class _CreateSpaceButton extends StatelessWidget {
// const _CreateSpaceButton();
// @override
// Widget build(BuildContext context) {
// return FlowyButton(
// text: FlowyText.regular(LocaleKeys.space_createNewSpace.tr()),
// iconPadding: 10,
// leftIcon: const FlowySvg(
// FlowySvgs.space_add_s,
// ),
// leftIconSize: const Size.square(20),
// onTap: () {
// PopoverContainer.of(context).close();
// _showCreateSpaceDialog(context);
// },
// );
// }
// void _showCreateSpaceDialog(BuildContext context) {
// final spaceBloc = context.read<SpaceBloc>();
// showDialog(
// context: context,
// builder: (_) {
// return Dialog(
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12.0),
// ),
// child: BlocProvider.value(
// value: spaceBloc,
// child: const CreateSpacePopup(),
// ),
// );
// },
// );
// }
// }
@@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
class RoundUnderlineTabIndicator extends Decoration {
const RoundUnderlineTabIndicator({
this.borderRadius,
this.borderSide = const BorderSide(width: 2.0, color: Colors.white),
this.insets = EdgeInsets.zero,
required this.width,
});
final BorderRadius? borderRadius;
final BorderSide borderSide;
final EdgeInsetsGeometry insets;
final double width;
@override
Decoration? lerpFrom(Decoration? a, double t) {
if (a is UnderlineTabIndicator) {
return UnderlineTabIndicator(
borderSide: BorderSide.lerp(a.borderSide, borderSide, t),
insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!,
);
}
return super.lerpFrom(a, t);
}
@override
Decoration? lerpTo(Decoration? b, double t) {
if (b is UnderlineTabIndicator) {
return UnderlineTabIndicator(
borderSide: BorderSide.lerp(borderSide, b.borderSide, t),
insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!,
);
}
return super.lerpTo(b, t);
}
@override
BoxPainter createBoxPainter([VoidCallback? onChanged]) {
return _UnderlinePainter(this, borderRadius, onChanged);
}
Rect _indicatorRectFor(Rect rect, TextDirection textDirection) {
final Rect indicator = insets.resolve(textDirection).deflateRect(rect);
final center = indicator.center.dx;
return Rect.fromLTWH(
center - width / 2.0,
indicator.bottom - borderSide.width,
width,
borderSide.width,
);
}
@override
Path getClipPath(Rect rect, TextDirection textDirection) {
if (borderRadius != null) {
return Path()
..addRRect(
borderRadius!.toRRect(_indicatorRectFor(rect, textDirection)),
);
}
return Path()..addRect(_indicatorRectFor(rect, textDirection));
}
}
class _UnderlinePainter extends BoxPainter {
_UnderlinePainter(
this.decoration,
this.borderRadius,
super.onChanged,
);
final RoundUnderlineTabIndicator decoration;
final BorderRadius? borderRadius;
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
assert(configuration.size != null);
final Rect rect = offset & configuration.size!;
final TextDirection textDirection = configuration.textDirection!;
final Paint paint;
if (borderRadius != null) {
paint = Paint()..color = decoration.borderSide.color;
final Rect indicator = decoration._indicatorRectFor(rect, textDirection);
final RRect rrect = RRect.fromRectAndCorners(
indicator,
topLeft: borderRadius!.topLeft,
topRight: borderRadius!.topRight,
bottomRight: borderRadius!.bottomRight,
bottomLeft: borderRadius!.bottomLeft,
);
canvas.drawRRect(rrect, paint);
} else {
paint = decoration.borderSide.toPaint()..strokeCap = StrokeCap.round;
final Rect indicator = decoration
._indicatorRectFor(rect, textDirection)
.deflate(decoration.borderSide.width / 2.0);
canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint);
}
}
}
Loaded 100 of 1261 files, more files were not shown because too many files have changed in this diff. Show more