V5 (#1222)
* chore(flutter): change BoxFit of VLC player * chore(flutter): remove unused imports * chore(flutter sample): tweak breakpoints, ignore some lints * chore(flutter): minor code readability improvements * chore(flutter): extract out platform_widget_builder to its own "package" * fix(flutter): fix a theme bug with PlatformDialog * chore(persistence): test plugin on macOS * Generated the macos runner for the example * Re-generated the android runner because it was using the v1 embedding * chore(persistence): test plugin on web * chore(persistence): add Windows runner to example * chore(persistence): add linux runner to example * feat(flutter): branded context menus * chore(flutter): fix bug where lines would be shown in between menu items * chore(flutter): import/export fixes * chore(flutter): move file * chore(flutter): /src/channel Also refactors several /channel widgets to use class widgets instead of methods. Some methods, however, have been left as those cases do actually produce cleaner code. chore(flutter): fix bad directory placement chore(flutter): fix bad directory placement * chore(flutter): more directory organization * chore(flutter): navigator improvements * chore(flutter): void async improvements * chore(flutter): fix imports/exports * docs(flutter): documentation updates for /attachment * docs(flutter): doc updates For: * /avatars * /bottom_sheets * /channel * feat(flutter): selectable message text on web & desktop * Squashed commit of the following: commit 50b249edb6b2ee2d744e4e00e97f81979026aab4 Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 16 14:55:05 2022 -0400 chore(flutter): wrapping up docs commit 7d289ae45e20c0109bcdb4496f7b693e2adc4ef9 Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 16 14:42:09 2022 -0400 chore(flutter): more docs & typedefs commit 6b36f6f3864f15b52e441d58ee7e62577ff8f843 Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 16 14:22:34 2022 -0400 chore(flutter): more doc fixes commit 1a9a6de3c836e9ee5d6373033f1d9a7da830bddc Merge: 59188f5d 88abf99f Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 16 13:17:52 2022 -0400 Merge branch 'desk_web_dev' into utils commit 59188f5d9c1dd68edd531086cdfd9d42faf93fc8 Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 16 12:48:16 2022 -0400 chore(flutter): add todo commit 9b4a986ca5757f284677eb5335637d11ccb1d642 Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 16 12:43:12 2022 -0400 chore(flutter): more docs and typedefs updates commit 8786d2dd82b94f673b36371fb2b33a1ebcc9840a Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 16 12:14:14 2022 -0400 chore(flutter): more typedef and doc template updates commit 9e906beb0e7a054c13a32c8c8c5b0e2a9e27bb21 Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 16 10:46:04 2022 -0400 chore(flutter): create /uitils and move files Fixes subsequent imports/exports * chore(flutter): update file_selector plugin * chore(flutter): include utils/typedefs.dart in library export * Squashed commit of the following: commit 501228afa614b0d740858dec1ebedd3f671e2c96 Author: GroovinChip <groovinchip@gmail.com> Date: Thu Mar 17 16:18:12 2022 -0400 feat(flutter): ensure fullscreen media feature works on ALL platforms commit cd0aab92bc7d90af03258829a5aae66125578af5 Author: GroovinChip <groovinchip@gmail.com> Date: Thu Mar 17 14:26:48 2022 -0400 feat(flutter): working conditional import for initializing VLC * fix(flutter): remove stray import causing web to break * chore(flutter): ensure context menus can show on web in sample * feat(flutter) FullScreenMedia context menus for Web * feat(flutter): branded context menus * chore(flutter): no success dialog on file download (web) * chore(flutter): remove platform UI packages * chore(flutter): properly show/hide delete message menu item Also deletes deprecated PlatformDialog * feat(flutter): 'view info' channel preview context menu * feat(flutter): more menu actions for ChannelPreview Also new ConfirmationDialog and add missing docs * feat(flutter/localizations): translations related to channel mutes * chore(flutter): remove prints and fix super.didChangeDependencies order * chore(flutter): rename some things in ChannelBottomSheet * chore(flutter): more sensible name in TypingIndicator * chore(flutter): remove unused code in MessageWidget * feat(flutter): ChannelInfoDialog * chore(flutter): organize files more Also refactors out some internal MessageListView widgets * fix(flutter): fix render overflow issue * chore(flutter): move MediaListView * chore(flutter): remove unused imports * fix(flutter): fix weird share menu placement on macos * chore(flutter): clean up fullscreenmedia conditional imports * chore(flutter): adjust package exports * chore(flutter): update dart_vlc * chore(flutter): clean up platform checks * chore(flutter): run flutter format on lib * chore(flutter): minor sample cleanup * chore(flutter): ensure vlc doesn't get initialized in non-desktop testing environments * test(flutter): reformat existing tests * fix(flutter): pass onThreadReplyTap down to ThreadReplyButton * First round of new tests commit a90bed427521123fdaa4c5e276744a224fac2b68 Author: GroovinChip <groovinchip@gmail.com> Date: Thu Apr 7 12:34:19 2022 -0400 test(flutter): Username test commit d7bc179c20eae2713c9248191518bf1d955104a9 Author: GroovinChip <groovinchip@gmail.com> Date: Thu Apr 7 12:30:47 2022 -0400 chore(flutter): remove unused imports commit 09c9517de2917dc5ccd7de019b50a7875386991c Author: GroovinChip <groovinchip@gmail.com> Date: Thu Apr 7 12:29:58 2022 -0400 test(flutter): ThreadSeparator test commit 9422d1129211aaaac9a51ca4d29fe94bde06593c Author: GroovinChip <groovinchip@gmail.com> Date: Thu Apr 7 12:19:43 2022 -0400 chore(flutter): rename LoadingIndicator parameter commit 63af943a1a16e78a1e0377d76176420bd7e1fbc7 Author: GroovinChip <groovinchip@gmail.com> Date: Thu Apr 7 11:47:46 2022 -0400 test(flutter): FloatingDateDivider test commit d8e67cf6fca2b76ac091a9047104c0910d5fdfef Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 15:19:39 2022 -0400 test(flutter): InputAttachment tests commit d2b7d5dd3304c8be6f74bf5b0281d7a95dd027ec Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 14:41:40 2022 -0400 test(flutter): DmCheckbox tests commit 01567f235a2f00880bb530b232489e3790159b25 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 14:16:08 2022 -0400 chore(flutter): remove unused imports commit c9625858e840df6082506ae9374f1d2710053cbd Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 14:15:44 2022 -0400 test(flutter): CountdownButton tests commit d770474bb11ee8b36a3b0e18db31695fd812a6b3 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 14:11:37 2022 -0400 test(flutter): CommandButton tests commit 95e63706d1dd971373d39d471894a38bb2a634f2 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 14:07:43 2022 -0400 test(flutter): ClearInputItem tests commit 4156586945c91408cb7943b6c57c2ae560a2857c Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 14:03:59 2022 -0400 test(flutter): AttachmentButton tests commit bba042806326999aabf338636c6e33900de86160 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 13:00:42 2022 -0400 test(flutter): golden test for SendButton commit e2cd0f00b9eeb8d81399f6316edb298234de12dc Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 12:56:52 2022 -0400 chore(flutter): deal with lints commit b4e045f9cbd54c4d42c3f30acfe72d5f8ccf9d82 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 12:56:00 2022 -0400 test(flutter): SendButton test commit 4e3d5db0bddb97e508fcdda2e7272fe31e70808f Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 12:38:21 2022 -0400 test(flutter): KeyboardShortcutRunner tests commit 0dfd7a0bd6fd6bc76902e87f62ac29da0b01bac6 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 12:17:30 2022 -0400 test(flutter): UploadProgressIndicator commit ec50c8aea716bdc19e4308e367c8331211c61ea3 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 11:47:53 2022 -0400 test(flutter): SendingIndicator tests commit e87bd1450fb5bfb713a867c7e831968f76428848 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 11:32:35 2022 -0400 test(flutter): more gallery tests commit 93d46ca649277fe6d289a70f0bdd23584fd0ab94 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 11:17:29 2022 -0400 test(flutter): remove bad mock commit c7b03a39f0f3ca7a334dfb908f3473b5e4deab38 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 10:45:45 2022 -0400 chore(flutter): minor refactor commit 33aeb7b5ed398287cb930f3b4702f47a82fce2c6 Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 09:49:07 2022 -0400 test(flutter): MessageDialog tests commit 220b3ae591e135d9606254666e04d4771945d0ae Author: GroovinChip <groovinchip@gmail.com> Date: Tue Mar 29 09:42:51 2022 -0400 test(flutter): DeleteMessageDialog tests commit db508478bb96749971f563de87b6c2a49e1e4e57 Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 14:46:41 2022 -0400 test(flutter): ConfirmationDialog tests commit e8172dad6bfe18f40b65554ef2c6c16d7437cb56 Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 14:35:50 2022 -0400 test(flutter): ChannelInfoDialog test commit 04c21458a601af3c3f4a5fc5ce9e8b61493c208c Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 13:57:02 2022 -0400 test(flutter): DownloadMenuItem tests commit afee1dace543e2fef8543f9b035f684b2ee00f3b Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 13:53:21 2022 -0400 test(flutter): StreamChatContextMenuItem tests commit 4616b1a973d0cf5224b5a71914c9908339c8462a Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 13:02:07 2022 -0400 test(flutter): golden test for ErrorAlertSheet commit 22fb8241ae333f57ade8e8550f87695929836b2b Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 12:59:01 2022 -0400 test(flutter): golden test for EditMessageSheet commit 0be79964827f344ba66e99cb9e5bd3d627aed01d Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 12:56:08 2022 -0400 test(flutter): golden test for AttachmentModalSheet commit 0ae842a98143478885bc211c788353c8bca57b40 Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 12:53:26 2022 -0400 test(flutter): GroupAvatar tests commit 697d294c6c7e1bba1d9089a53d5c60d8609ea64e Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 12:30:25 2022 -0400 test(flutter): UserAvatar tests commit c03ba4124068c83d3a49c4db00ca18a659801c23 Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 11:04:55 2022 -0400 test(flutter): ErrorAlertSheet test commit 3414f28530b0e22614b83af20361f21dd4967fae Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 10:45:37 2022 -0400 test(flutter): EditMessageSheet test commit b2962b641dfbf14c11d3da7f80a4be27f9980d90 Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 10:37:18 2022 -0400 test(flutter): AttachmentModalSheet test test(flutter): AttachmentModalSheet test chore(flutter): update exports commit 7d7e278692c65dea84e7ef3b2d3c1311ebb28408 Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 09:52:29 2022 -0400 test(flutter): more organization commit 4479b649835acc62d8a0553f9505a0af8077abfc Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 09:33:06 2022 -0400 test(flutter): tweak AttachmentHandler tests commit b4a41106567e3c08105ce8dc272b1ebf40b3b0bb Author: GroovinChip <groovinchip@gmail.com> Date: Mon Mar 28 09:24:00 2022 -0400 chore(flutter): streamline attachment handling * MobileAttachmentHandler for streamlining related code for mobile * Break out DesktopAttachmentHandler into its own file * Update minSdk version in Android runner commit bfb06d31b79cd941d8b611c7e89e2d7728a394cb Merge: a44b3b39 15c83d03 Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 13:00:27 2022 -0400 Merge branch 'desk_web_dev' into tests commit a44b3b390adc25a00d1597bb1460973e8555fa9e Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 12:59:14 2022 -0400 chore(flutter): start cleaning up test dir commit de104a3491d942d12f72dd66152b62c6e8b10a5b Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 12:56:48 2022 -0400 test(flutter): VideoAttachment test commit 4748a1c081a3272f7fdbdeb69c501344e221e01e Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 12:52:30 2022 -0400 test(flutter): UrlAttachment test commit 54408ef70f07c60adf8350553fdf3173e5edd7c7 Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 12:48:24 2022 -0400 test(flutter): ImageGroup test commit 811cff069e490c6bd03be0c0ac2f5ec86fb990e9 Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 12:46:04 2022 -0400 test(flutter): ImageAttachment test commit a72677976c6524e76793432b0520e99cac4e7a62 Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 12:34:06 2022 -0400 test(flutter): GiphyAttachment test commit 32ddac022e2c149726b4b6607f41850d556055b8 Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 12:28:01 2022 -0400 test(flutter): FileAttachment test commit 6534958cab775be1fb9bda56e67caa4746aa1502 Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 12:14:25 2022 -0400 test(flutter): AttachmentError test commit 718a83c10c4e883ce01c9c002ced24de671d30b9 Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 12:06:43 2022 -0400 test(flutter): AttachmentUploadStateBuilder test commit a05108c460e36b2e425e4ec06f8bf408b0c20e97 Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 11:44:34 2022 -0400 chore(flutter): minor cleanup commit 6e73038ac3714a2bd96f8c587af7f743bf2b2023 Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 11:43:23 2022 -0400 test(flutter): AttachmentTitle test commit c6ee52ec951877feef31dc1427eec3bfc2bd8fcb Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 11:43:11 2022 -0400 test(flutter): minor cleanup commit 09845cd845aa8c546043f4ae04805a8e6f53f61c Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 11:42:52 2022 -0400 chore(flutter): improve device segmentation & vlc init check commit 172e6b25a064b5147b7641f29ebff3c920156f70 Author: GroovinChip <groovinchip@gmail.com> Date: Fri Mar 25 10:37:43 2022 -0400 test(flutter): attachment_handler tests commit 5f5a91c70cd7c069f39518f1f50bb74ab484c7e9 Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 23 13:55:27 2022 -0400 test(flutter): test PlatformWidgetBuilder & DesktopWidgetBuilder commit 5a457b7557cb4127b74ee022389a24e5e9150601 Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 23 13:55:05 2022 -0400 chore(flutter): make base platform widgets testable Also fix environment check in stream_chat.dart commit 88fb16018103c581236aeebd978e705b223b2243 Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 23 11:59:19 2022 -0400 chore(flutter): add readme to conditional_parent_widget library commit e9307f8a6a5639d59358c245f033f9d9b209fe20 Author: GroovinChip <groovinchip@gmail.com> Date: Wed Mar 23 11:54:54 2022 -0400 test(flutter): ConditionalParentBuilder tests * chore(flutter): add thumblr dependency * chore(flutter): fix typos * chore(flutter): properly order super calls * feat(flutter) video thumbnails on desktop * chore(flutter): fix incorrect file name * chore(flutter): fix docs for generateVideoThumbnail * chore(flutter): remove incorrect comment * chore(flutter): run flutter format * chore(flutter): ignore lint * chore(flutter): flutter format * chore(flutter): fix tests * fix(flutter): fix MessageListView bug caused by rebase * chore(flutter): migrate desktop stuff to v4/stream_message_input.dart * chore(flutter): migrate over desktop changes for message replies * chore(flutter): fix message replies on desktop * chore(flutter): address lints * chore(flutter): update dart_vlc * chore(flutter): melos clean & update photo_view * fix(ui): port #1118 * fix(ui): use attachmentButtonBuilder * fix(ui): fix file attachment * format * fix(ui): fix message bottom row * chore(flutter sample): use modern window look * chore(flutter): pass userAvatarBuilder through to MessageWidgetContent Also pass onUserAvatarTap & userAvatarBuilder through to UserAvatarTransform * chore: fix MessageWidget menu actions * fix(flutter): fix "enter" sending empty messages in StreamMessageInput * fix(flutter): fix Giphys not loading in quoted messages * chore(flutter): update thumblr * feat(flutter): keyboard navigation for gallery items * fix(ui): fix double date divider * fix(ui): add scroll to bottom back * fix(ui): use mouse region * fix(ui): remove backbutton in desktop example * fix(ui): rename full screen media widget * feat(flutter): elevation prop for gallery header * chore(flutter): update dart_vlc * fix(flutter): fix "show in chat" showing when it shouldn't * fix(flutter): fix "show in chat" for mobile * docs(flutter): minor doc update * chore(flutter): add onMessageSwiped in example app * chore(flutter): pass usernameBuilder through to BottomRow * fix(flutter): hacky fix for chonky link previews * fix(flutter): fix chonky text messages * feat(flutter): press "esc" to close gallery Also refactors KeyboardShortcutRunner API * chore(flutter): fix innaccurate name * fix(ui): fix attachment + long text sizing * add key to channelpage * fix(ui): better example * fix format * fix example * chore(flutter): revert modern window look * improvement(flutter): improve `GalleryNavigationItem` * Make buttons more visible * Spacing from the sides of the screen * fix video thumbnail * fix split view * fix(ui): fix giphy in thread * feat(flutter): desktop message reactions commit 4b880ecb6d4dcbf315a7d41d863174817a9c530a Author: Reuben Turner <groovinchip@gmail.com> Date: Thu Jun 9 12:25:21 2022 -0400 fix(flutter): fixes for desktop reactions * Fix reactions for reverse messages * Ensure reactions near the edges of MessageListView show properly commit f511513f8dbb272397e81d08bad33ee60108d738 Author: Salvatore Giordano <salvatoregiordanoo@gmail.com> Date: Thu Jun 9 16:43:03 2022 +0200 fix(ui): fix DesktopReactionBuilder and Portal commit c4885648330552f5212c5ba6b771c0f07023f6ae Merge: 2b710a2b 72b34c21 Author: Reuben Turner <groovinchip@gmail.com> Date: Wed Jun 8 14:12:44 2022 -0400 Merge branch 'desk_web_dev' into desktop_reactions commit 2b710a2bb610326123d5b7f75c26caa705114c9b Author: Reuben Turner <groovinchip@gmail.com> Date: Mon Jun 6 12:19:12 2022 -0400 chore(flutter): add debug props commit e24dabc1de12da8dce697b6d4e1a3617e9835549 Author: Reuben Turner <groovinchip@gmail.com> Date: Mon Jun 6 12:00:03 2022 -0400 feat(flutter): first pass at desktop reactions popup on mouse hover * fix(flutter): potential fix for #1199 * fix(flutter): fix non-reversed reactions placement * refactor(flutter): extract bottom reaction, stacked reaction with debug props * fix(flutter): fixes for desktop reactions * Fix bottom row being rendered above reaction buttons * Fix padding issues related to avatars * chore(flutter): add back lost changes from 2b1fb6a Also removed commented code * chore(flutter): stacked reactions update * Show "You" if userId matches the reaction username (per design) * Ensure long names don't throw off alignment * chore(flutter): format * fix(ui): fix mime type generation * chore(flutter): tweak desktop video * chore(flutter): add some missing things from develop * merge develop * fix merge * fix merge * format * fix tests * increate test action timeout * remove test * refactor(ui,core): remove deprecated code and cleanup * run builder * update coverage threasholds * refactor(ui): remove arrow function from countdown button * Update packages/stream_chat_flutter/lib/src/message_input/countdown_button.dart * Update packages/stream_chat_flutter/lib/src/message_input/countdown_button.dart * update comment * fix(ui): fix attachment + link solution * fix(ui): fix attachment + link solution * chore(flutter) update changelog and version * chore(localizations): update changelog and versions * chore(llc,core,ui,persistence,localizations): update changelog and pubspec * chore(llc,core,ui,persistence,localizations): update changelog and pubspec * fix example * fix deps * fix(ui): fix enter sending the message while in slowmode * fix(ui): fix reaction list * fix web * fix(ui): fix image download when using assetUrl * fix(ui): fix typo in changelog * fix(ui): fix gallery navigation * fix(ui): fix giphy sending size * fix(ui): add keyboard shorcuts to gallery view * fix(ui): fix gallery footer * fix(core): allow scroll notification bubbling in lazy load scroll view * fix(ui): fix lazy load scroll view * fix(ui): fix loading to unread position * fix(ui): fix last message preview * chore(ui): update changelog * fix tests * fix(llc): fix connecting while connecting and disconneting (#1237) * fix(llc): fix connecting while connecting and disconneting * chore(llc): update changelog * chore(llc): add test * chore(llc): improve test * fix(llc): _resetRequestFlags regardless of the connection status * feat(llc): add support for AttachmentFileUploaderProvider. (#1246) * feat(llc): add support for `AttachmentFileUploaderProvider`. Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(llc): update CHANGELOG.md Signed-off-by: xsahil03x <xdsahil@gmail.com> * refactor(llc): Remove deprecated attachmentFileUploader field. Signed-off-by: xsahil03x <xdsahil@gmail.com> * port no localization * fix(ui): fix jiffy locale initialization (#1255) * fix(ui): fix jiffy locale initialization * chore(ui): update changelog * fix(ui): cleanup * chore(llc,core,ui,persistence,localizations): align changelog * chore(llc,core,ui,persistence,localizations): add links to 4.x changelogs * fix(ui): fix date divider not showing (#1259) * fix(ui): fix date divider not showing * chore(ui): update changelog * chore(ui): minor changes Signed-off-by: xsahil03x <xdsahil@gmail.com> * fix(ui): fix separator Signed-off-by: xsahil03x <xdsahil@gmail.com> Co-authored-by: Sahil Kumar <xdsahil@gmail.com> * chore(ui): add contextmenu requirements for web (#1261) * fix unread separator * feat(ui): `StreamChatConfiguration` class (#1128) * feat: StreamChatConfiguration class See #1125 for more details * chore(flutter): update changelog * fix analysis * cleanup * feat(ui): add enforceUniqueReactions to `StreamChatConfiguration` widget * add tests * refactor(ui): rename StreamChatConfigurationProvider and StreamChatConfiguration * refactor(ui): rename StreamChat.config to streamChatConfigData * refactor(ui): make enforceUniqueReactions non nullable * chore(ui): fix analysis errors * refactor(ui): make config fields non nullable * chore(ui): format Co-authored-by: Salvatore Giordano <salvatoregiordanoo@gmail.com> * feat(ui, core): add StreamAutocomplete (#1263) * feat(ui, core): add stream_autocomplete.dart Signed-off-by: xsahil03x <xdsahil@gmail.com> * refactor(core, ui): refactor message input controller Signed-off-by: xsahil03x <xdsahil@gmail.com> * fix(ui): minor fixes Signed-off-by: xsahil03x <xdsahil@gmail.com> * feat(ui): add StreamAutocompleteOptions widget Signed-off-by: xsahil03x <xdsahil@gmail.com> * Update packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart * fix(ui): cleanup * chore(core, ui): cleanup Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ui): ignore no empty block * chore(ui): more cleanup Signed-off-by: xsahil03x <xdsahil@gmail.com> * fix(ui): fix command button for bigger screens, other fixes. Signed-off-by: xsahil03x <xdsahil@gmail.com> Co-authored-by: Salvatore Giordano <salvatoregiordanoo@gmail.com> * fix(ui): increase default file limit to 100MB (#1279) * fix(ui): increase default file limit to 100MB * Update packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart * fix(ui): export kDefaultMaxAttachmentSize and use it in `StreamAttachmentPicker` default * feat(ui): show dialog after clicking on the camera button and permission is denied (#1262) * feat(ui): show dialog after clicking on the camera button and permission is denied * chore(ui): update changelog * Remove permission_handler plugin and refactor * cleanup * cleanup * add tests * fix(ui): reset the permission state on resume * cleanup * fix concurrent calls * fix(ui): cleanup * fix(ui): add try catch * fix(llc, ui): copy file to tempdir before uploading a file and do not serialize bytes (#1285) * fix(llc, ui): copy file to tempdir before uploading a file and do not serialize bytes * fix(llc): do not save failed messages with attachments on web * fix(llc): replace every with any * chore(llc): add test * fix analysis * fix analysis * chore(llc,core,ui,persistence): align changelog with v4 * fix(ui): do not linkify system message mentions * chore(llc,core,ui,persistence,localizations): update changelog and pubspec (#1289) * chore(ui): update deps * chore(llc,core,ui,persistence,localizations): update changelog and pubspec * chore(ui): publish beta.2 * feat(core,ui): add StreamMemberListView, StreamMemberGridView, and StreamMemberListController (#1291) * feat(core,ui): add StreamMemberListView, StreamMemberGridView, and StreamMemberListController * chore(core,ui): update changelog * fix(ui): fix exports * chore(ui): add tests * chore(ui): add StreamMemberGridTile typedef * chore(ui): minor changes Signed-off-by: xsahil03x <xdsahil@gmail.com> Co-authored-by: Sahil Kumar <xdsahil@gmail.com> * fix(ui): do not send empty message * refactor(ui,localization): removed emoji picker and connected (#1292) * refactor(ui,localization): removed emoji picker and connected * chore(ui,localizations): update changelog * chore(repo): update ui coverage threashold * doc(ui): add autocomplete_triggers guide * chore(ui): add tests for isOnlyEmoji * chore(ui): add tests for isOnlyEmoji * feat(ui): add `StreamAutocompleteTrigger.triggerOnlyAfterSpace` * fix(llc): Fix `Channel.removeMessage` not able to remove thread message. (#1294) Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ui): flutter format Signed-off-by: xsahil03x <xdsahil@gmail.com> * fix(ui): fix `stopTyping` unhandled exceptions when network is off or spotty. (#1296) * feat(llc, ui): Introduce `keyStrokeHandler` to properly handle keyStrokes. Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ui): update CHANGELOG.md Signed-off-by: xsahil03x <xdsahil@gmail.com> * test(llc): add key_stroke_handler_test.dart Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ui): update CHANGELOG.md Signed-off-by: xsahil03x <xdsahil@gmail.com> * fix(core): only handle the channel list event if the state is success. Signed-off-by: xsahil03x <xdsahil@gmail.com> * feat(llc): add `hide_history` flag in `channel.addMembers`. Signed-off-by: xsahil03x <xdsahil@gmail.com> * refactor(llc): Convert positional argument to optional arguments Signed-off-by: xsahil03x <xdsahil@gmail.com> * test(llc): update tests Signed-off-by: xsahil03x <xdsahil@gmail.com> * perf: improve image resizing * fix(core): use copyWith while setting controller items. Signed-off-by: xsahil03x <xdsahil@gmail.com> * feat(ui): Added `thumbnailSize`, `thumbnailResizeType`, and `thumbnailCropType` params to `StreamMessageWidget` and `StreamAttachmentPicker` to customize the appearance of image thumbnails. Signed-off-by: xsahil03x <xdsahil@gmail.com> * fix analysis * Release v4.5.0 (#1308) * chore(llc, ui, core): bump version to v4.5.0 Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ui, core): update CHANGELOG.md Signed-off-by: xsahil03x <xdsahil@gmail.com> Signed-off-by: xsahil03x <xdsahil@gmail.com> * third party video integration * fix llc * refactor: clean up and add docs * chore(llc): update changelog * chore(llc): add tests * chore(llc): add more tests * fix analysis * chore(llc): add more tests * chore(llc): add more tests * fix(llc): only start listening to event once connected. Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(llc): update CHANGELOG.md Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(llc): move files to correct folders. Signed-off-by: xsahil03x <xdsahil@gmail.com> * fix(ui): Fix message text hiding because of a flutter bug (#1327) * fix(ui): remove clip * chore(ui): update changelog * cleanup * fix(ui): hitting "enter" on the android keyboard sends the message instead of going to a new line. Signed-off-by: xsahil03x <xdsahil@gmail.com> * feat(ui): add support for additional text field params in message input. Signed-off-by: xsahil03x <xdsahil@gmail.com> * fix analysis * remove print * chore(llc,core,ui): update pubspec and changelogs (#1333) * fix(ui): show placeholder for web video thumbs * fix file download on web * fix reply and show buttons * remove tests * treat svg as files * fix(ui): change default image resize to clip * solve some todos * polish v5 fixing analysis issues * feat(ui): stream attachment picker bottom sheet (#1336) * refactor(ui): improve stream_attachment_handler.dart, add stream_attachment_picker_bottom_sheet.dart Signed-off-by: xsahil03x <xdsahil@gmail.com> * feat(ui): add copyWith in command_button.dart Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ui): remove permission_manager.dart Signed-off-by: xsahil03x <xdsahil@gmail.com> * fix(ui): some fixes Signed-off-by: xsahil03x <xdsahil@gmail.com> * fix(ui): more fixes Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ui): fix lints, add doc comments Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ci): update flutter version Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ui): more fixes Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ui): more fixes Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ui): more fixes Signed-off-by: xsahil03x <xdsahil@gmail.com> * chore(ci): update melos and flutter version Signed-off-by: xsahil03x <xdsahil@gmail.com> Signed-off-by: xsahil03x <xdsahil@gmail.com> Co-authored-by: Salvatore Giordano <salvatoregiordanoo@gmail.com> * remove todo * update changelog * update changelogs * revert * update coverage threasholds Signed-off-by: xsahil03x <xdsahil@gmail.com> Co-authored-by: GroovinChip <groovinchip@gmail.com> Co-authored-by: Sahil Kumar <xdsahil@gmail.com> Co-authored-by: Gordon Hayes <pggordonhayes@gmail.com> Co-authored-by: Sacha Arbonel <sacha.arbonel@hotmail.fr>
This commit is contained in:
committed by
GitHub
parent
3e1d6e91c7
commit
4f2542291c
@@ -1,9 +1,9 @@
|
||||
name: Dart Code Metrics
|
||||
|
||||
env:
|
||||
flutter_version: "3.0.0"
|
||||
flutter_version: "3.3.3"
|
||||
folders: "lib, test"
|
||||
melos_version: "2.1.0"
|
||||
melos_version: "2.7.1"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
@@ -2,8 +2,8 @@ name: stream_flutter_workflow
|
||||
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
flutter_version: "3.0.0"
|
||||
melos_version: "2.1.0"
|
||||
flutter_version: "3.3.3"
|
||||
melos_version: "2.7.1"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
test:
|
||||
runs-on: macos-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: "Git Checkout"
|
||||
uses: actions/checkout@v2
|
||||
@@ -111,22 +111,27 @@ jobs:
|
||||
uses: VeryGoodOpenSource/very_good_coverage@v1.1.1
|
||||
with:
|
||||
path: packages/stream_chat/coverage/lcov.info
|
||||
min_coverage: 80
|
||||
min_coverage: 79
|
||||
- name: "Stream Chat Persistence Coverage Check"
|
||||
uses: VeryGoodOpenSource/very_good_coverage@v1.1.1
|
||||
with:
|
||||
path: packages/stream_chat_localizations/coverage/lcov.info
|
||||
min_coverage: 88
|
||||
- name: "Stream Chat Persistence Coverage Check"
|
||||
uses: VeryGoodOpenSource/very_good_coverage@v1.1.1
|
||||
with:
|
||||
path: packages/stream_chat_persistence/coverage/lcov.info
|
||||
min_coverage: 95
|
||||
min_coverage: 97
|
||||
- name: "Stream Chat Flutter Core Coverage Check"
|
||||
uses: VeryGoodOpenSource/very_good_coverage@v1.1.1
|
||||
with:
|
||||
path: packages/stream_chat_flutter_core/coverage/lcov.info
|
||||
min_coverage: 90
|
||||
min_coverage: 30
|
||||
- name: "Stream Chat Flutter Coverage Check"
|
||||
uses: VeryGoodOpenSource/very_good_coverage@v1.1.1
|
||||
with:
|
||||
path: packages/stream_chat_flutter/coverage/lcov.info
|
||||
min_coverage: 67
|
||||
min_coverage: 44
|
||||
|
||||
draft-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -198,7 +198,6 @@ analyzer:
|
||||
exclude:
|
||||
- packages/*/lib/**/*.g.dart
|
||||
- packages/*/example/**
|
||||
- packages/*/lib/src/emoji
|
||||
- packages/*/lib/**/*.freezed.dart
|
||||
- packages/*/test/**
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ analyzer:
|
||||
- dart_code_metrics
|
||||
exclude:
|
||||
- packages/*/lib/**/*.g.dart
|
||||
- packages/*/lib/src/emoji/**
|
||||
- packages/*/lib/scrollable_positioned_list/**
|
||||
- packages/*/lib/**/*.freezed.dart
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ If you're not sure if you've set up push notifications correctly (e.g. you don't
|
||||
11. After configuring [stream-cli](https://github.com/GetStream/stream-cli) paste the following command on command line using your user ID
|
||||
|
||||
```shell
|
||||
stream-cli chat test-push --user-id <USER-ID>
|
||||
stream chat:push:test -u <USER-ID>
|
||||
```
|
||||
|
||||
You should get a test push notification
|
||||
|
||||
@@ -223,7 +223,7 @@ If you're not sure whether you've set up push notifications correctly, for examp
|
||||
9. Tap on `Device ID` and copy it
|
||||
11. After configuring [stream-cli](https://github.com/GetStream/stream-cli), run the following command using your user ID:
|
||||
```shell
|
||||
stream-cli chat test-push --user-id <USER-ID>
|
||||
stream chat:push:test -u <USER-ID>
|
||||
```
|
||||
|
||||
You should get a test push notification 🥳
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
id: autocomplete_triggers
|
||||
title: Adding Custom Autocomplete Triggers
|
||||
---
|
||||
|
||||
Adding Custom Autocomplete Triggers
|
||||
|
||||
### Introduction
|
||||
|
||||
The [StreamMessageInput](../stream_chat_flutter/message_input.mdx) widget provides a way to add custom autocomplete triggers using the `StreamMessageInput.customAutocompleteTriggers` property.
|
||||
|
||||
By default we provide autocomplete triggers for mentions and commands, but it's very easy to add your custom ones.
|
||||
|
||||
### Add Emoji Autocomplete Trigger
|
||||
|
||||
To add a custom emoji autocomplete trigger, you must first create an `AutoCompleteOptions` widget.
|
||||
This widget will be used to show the autocomplete options.
|
||||
|
||||
For this example we're using two external dependencies:
|
||||
|
||||
- [emojis](https://pub.dev/packages/emojis)
|
||||
- [substring_highlight](https://pub.dev/packages/substring_highlight)
|
||||
|
||||
```dart
|
||||
import 'package:emojis/emoji.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:substring_highlight/substring_highlight.dart';
|
||||
|
||||
/// Overlay for displaying emoji that can be used
|
||||
class StreamEmojiAutocompleteOptions extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamEmojiAutocompleteOptions]
|
||||
const StreamEmojiAutocompleteOptions({
|
||||
super.key,
|
||||
required this.query,
|
||||
this.onEmojiSelected,
|
||||
});
|
||||
|
||||
/// Query for searching emoji.
|
||||
final String query;
|
||||
|
||||
/// Callback called when an emoji is selected.
|
||||
final ValueSetter<Emoji>? onEmojiSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final emojis = Emoji.all().where((it) {
|
||||
final normalizedQuery = query.toUpperCase();
|
||||
final normalizedShortName = it.shortName.toUpperCase();
|
||||
|
||||
return normalizedShortName.contains(normalizedQuery);
|
||||
});
|
||||
|
||||
if (emojis.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return StreamAutocompleteOptions<Emoji>(
|
||||
options: emojis,
|
||||
optionBuilder: (context, emoji) {
|
||||
final themeData = Theme.of(context);
|
||||
return ListTile(
|
||||
dense: true,
|
||||
horizontalTitleGap: 0,
|
||||
leading: Text(
|
||||
emoji.char,
|
||||
style: themeData.textTheme.headline6!.copyWith(
|
||||
fontSize: 24,
|
||||
),
|
||||
),
|
||||
title: SubstringHighlight(
|
||||
text: emoji.shortName,
|
||||
term: query,
|
||||
textStyleHighlight: themeData.textTheme.headline6!.copyWith(
|
||||
color: Colors.yellow,
|
||||
fontSize: 14.5,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textStyle: themeData.textTheme.headline6!.copyWith(
|
||||
fontSize: 14.5,
|
||||
),
|
||||
),
|
||||
onTap: onEmojiSelected == null ? null : () => onEmojiSelected!(emoji),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now it's time to use the `StreamEmojiAutocompleteOptions` widget.
|
||||
|
||||
```dart
|
||||
StreamMessageInput(
|
||||
customAutocompleteTriggers: [
|
||||
StreamAutocompleteTrigger(
|
||||
trigger: ':',
|
||||
minimumRequiredCharacters: 2,
|
||||
optionsViewBuilder: (
|
||||
context,
|
||||
autocompleteQuery,
|
||||
messageEditingController,
|
||||
) {
|
||||
final query = autocompleteQuery.query;
|
||||
return StreamEmojiAutocompleteOptions(
|
||||
query: query,
|
||||
onEmojiSelected: (emoji) {
|
||||
// accepting the autocomplete option.
|
||||
StreamAutocomplete.of(context).acceptAutocompleteOption(
|
||||
emoji.char,
|
||||
keepTrigger: false,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
```
|
||||
@@ -1,696 +0,0 @@
|
||||
---
|
||||
id: initialize_stream_chat_widget_tree
|
||||
sidebar_position: 15
|
||||
title: Initialize Stream Chat in Part of the Widget Tree
|
||||
---
|
||||
|
||||
If you’re creating a full-scale chat application, you probably want to have Stream Chat Flutter initialized at the top of your widget tree and the Stream user connected as soon as they open the application.
|
||||
|
||||
However, if you only need chat functionality in a part of your application, then it’ll be better to delay Stream Chat initialization to when it’s needed.
|
||||
This guide demonstrates three alternative ways for you to initialize Stream Chat Flutter for a part of your widget tree and to only connect a user when needed.
|
||||
|
||||
## What To Keep In Mind?
|
||||
|
||||
Before investigating potential solutions, let’s first take a look at the relevant Stream Chat widgets and classes.
|
||||
|
||||
Most of the Stream Chat Flutter UI widgets rely on having a [StreamChat](https://getstream.io/chat/docs/sdk/flutter/stream_chat_flutter/stream_chat_and_theming/) ancestor in the widget tree.
|
||||
The **StreamChat** widget is an [InheritedWidget](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html) that exposes the **StreamChatClient** through **BuildContext**.
|
||||
This widget also initializes the [StreamChatCore](../stream_chat_flutter_core/stream_chat_core.mdx) widget and the **StreamChatTheme**.
|
||||
|
||||
**StreamChatCore** is a **StatefulWidget** used to react to life cycle changes and system updates.
|
||||
When the app goes into the background, the WebSocket connection is closed.
|
||||
Conversely, a new connection is initiated when the app is back in the foreground.
|
||||
|
||||
What is important to take note of is that **a connection is only established if a user is connected**.
|
||||
|
||||
This means that if you have not yet called `client.connectUser(user, token)`, no connection will be made, and only background listeners will be registered to determine the app's foreground state.
|
||||
|
||||
## Option 1: Builder and Connect/Disconnect User
|
||||
|
||||
This option requires you to wrap your whole application with the **StreamChat** widget and to call `connectUser` and `disconnectUser` as needed.
|
||||
|
||||
This option is the easiest, however, it requires **StreamChat** to be at the top of each route and as a result, will have a slight overhead as it’ll create the above-mentioned Stream widgets that may not yet be needed.
|
||||
|
||||
### Exposing the StreamChat Widget
|
||||
|
||||
First, you must expose the client and base Stream Chat widgets to the whole application.
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
final client = StreamChatClient(
|
||||
'q29npdvqjr99',
|
||||
logLevel: Level.OFF,
|
||||
);
|
||||
|
||||
runApp(MyApp(client: client));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({
|
||||
Key? key,
|
||||
required this.client,
|
||||
}) : super(key: key);
|
||||
|
||||
final StreamChatClient client;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
builder: (context, child) {
|
||||
return StreamChat(client: client, child: child);
|
||||
},
|
||||
home: const HomeScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the above code, you:
|
||||
|
||||
1. Create a **StreamChatClient** instance
|
||||
2. Pass the instance to the **StreamChat** widget
|
||||
3. Expose **StreamChat** to the whole application within the **MaterialApp** `builder`
|
||||
|
||||
A few important things to note:
|
||||
|
||||
- The `builder` wraps the **StreamChat** widget for every route of our application. No matter where you are in the widget tree, you’ll be able to call `StreamChat.of(context)`.
|
||||
- The state will only be created once for our application, as **StreamChat** is a **StatefulWidget** and the position of **StreamChat** in the widget tree remains the same throughout the application lifecycle.
|
||||
- No connection will be made until you call `connectUser`.
|
||||
|
||||
### Connecting and Disconnecting Users
|
||||
|
||||
In **MaterialApp** above, the home page is set to **HomeScreen**. This screen could look something like the following:
|
||||
|
||||
```dart
|
||||
class HomeScreen extends StatelessWidget {
|
||||
const HomeScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: const Center(
|
||||
child: Text('Home Screen'),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => const ChatSetup()));
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.message,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This screen shows a floating action button that on click navigates the user to the **ChatSetup**. We want to connect and disconnect a Stream user only when they go to the chat setup screen.
|
||||
|
||||
```dart
|
||||
class ChatSetup extends StatefulWidget {
|
||||
const ChatSetup({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ChatSetup> createState() => _ChatSetupState();
|
||||
}
|
||||
|
||||
class _ChatSetupState extends State<ChatSetup> {
|
||||
late final Future<OwnUser> connectionFuture;
|
||||
late final client = StreamChat.of(context).client;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
connectionFuture = client.connectUser(
|
||||
User(id: 'USER_ID'),
|
||||
'TOKEN',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
client.disconnectUser();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: FutureBuilder(
|
||||
future: connectionFuture,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
switch (snapshot.connectionState) {
|
||||
case ConnectionState.waiting:
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
default:
|
||||
if (snapshot.hasError) {
|
||||
return Text('Error: ${snapshot.error}');
|
||||
} else {
|
||||
return const ChannelListPage();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Within `initState` you call `connectUser`; once the future for `connectUser` has completed a connection to the Stream API is established, you can then display the relevant Stream Chat UI widgets.
|
||||
|
||||
Once the **ChatScreen** widget is disposed of, then `disconnectUser` will be called within the `dispose` method.
|
||||
|
||||
### Caution
|
||||
|
||||
In this example, `disconnectUser` will **only** be called when the **ChatSetup** widget is disposed.
|
||||
|
||||
You need to ensure that this widget (route) is completely disposed of, or you need to call `disconnectUser` and `connectUser` manually when navigating to relevant parts of your application.
|
||||
|
||||
For example, let’s say you have a button within one of the chat screens to navigate to a completely different part of your app, then you want to make sure the **ChatScreen** route is disposed of by forcing it to be removed:
|
||||
|
||||
```dart
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: ((context) => const HomeScreen()),
|
||||
),
|
||||
(Route<dynamic> route) => false,
|
||||
);
|
||||
```
|
||||
|
||||
Or let’s say you wanted to pop back to the first route:
|
||||
|
||||
```dart
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
```
|
||||
|
||||
Both of these will ensure the route is disposed of and the user is disconnected as a result.
|
||||
|
||||
## Option 2: A Nested Navigator
|
||||
|
||||
Another approach would be to introduce a new [Navigator](https://api.flutter.dev/flutter/widgets/Navigator-class.html).
|
||||
This has the benefit that everything related to Stream chat is contained to a specific part of the widget tree.
|
||||
|
||||
### Defining Routes and Subroutes
|
||||
|
||||
In this example, our application has the following routes.
|
||||
|
||||
```dart
|
||||
const routeHome = '/';
|
||||
const routePrefixChat = '/chat/';
|
||||
const routeChatHome = '$routePrefixChat$routeChatChannels';
|
||||
const routeChatChannels = 'chat_channels';
|
||||
const routeChatChannel = 'chat_channel';
|
||||
```
|
||||
|
||||
For the `/chat/` subroutes (**routePrefixChat**), this approach initializes Stream Chat in our application and introduces a nested navigator.
|
||||
|
||||
Let’s explore the code:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Provider.value(
|
||||
value: navigatorKey,
|
||||
child: MaterialApp(
|
||||
navigatorKey: navigatorKey,
|
||||
initialRoute: routeHome,
|
||||
onGenerateRoute: (settings) {
|
||||
late Widget page;
|
||||
if (settings.name == routeHome) {
|
||||
page = const HomeScreen();
|
||||
} else if (settings.name!.startsWith(routePrefixChat)) {
|
||||
final subRoute = settings.name!.substring(routePrefixChat.length);
|
||||
page = ChatSetup(
|
||||
setupChatRoute: subRoute,
|
||||
);
|
||||
} else {
|
||||
throw Exception('Unknown route: ${settings.name}');
|
||||
}
|
||||
|
||||
return MaterialPageRoute<dynamic>(
|
||||
builder: (context) {
|
||||
return page;
|
||||
},
|
||||
settings: settings,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the above code you’re:
|
||||
|
||||
1. Creating a navigator key, passing it to `MaterialApp`, and exposing it to the whole application using Provider (you can expose it however you want).
|
||||
2. Creating `onGenerateRoute` that specifies what page to show depending on the route. Most importantly, if the route contains the **routePrefixChat,** it navigates to the **ChatSetup** page and passes in the remainder of the route.
|
||||
|
||||
For example, `Navigator.pushNamed(context, routeChatHome)` will navigate to the **ChatSetup** page and pass in the subroute **routeChatChannels.**
|
||||
|
||||
### Stream Chat Initialization, User Connection, and Nested Navigation
|
||||
|
||||
Within the **ChatSetup** widget, we’ll initialize Stream chat, connect a user, and create a new Navigator that handles the sub-navigation for the chat-specific pages.
|
||||
|
||||
```dart
|
||||
class ChatSetup extends StatefulWidget {
|
||||
const ChatSetup({Key? key, required this.setupChatRoute}) : super(key: key);
|
||||
|
||||
final String setupChatRoute;
|
||||
|
||||
@override
|
||||
State<ChatSetup> createState() => _ChatSetupState();
|
||||
}
|
||||
|
||||
class _ChatSetupState extends State<ChatSetup> {
|
||||
late final Future<OwnUser> connectionFuture;
|
||||
late final client = StreamChatClient(
|
||||
'KEY',
|
||||
logLevel: Level.OFF,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
connectionFuture = client.connectUser(
|
||||
User(id: 'USER_ID'),
|
||||
'TOKEN',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
client.disconnectUser();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: StreamChat(
|
||||
client: client,
|
||||
child: FutureBuilder(
|
||||
future: connectionFuture,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
switch (snapshot.connectionState) {
|
||||
case ConnectionState.waiting:
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
default:
|
||||
if (snapshot.hasError) {
|
||||
return Text('Error: ${snapshot.error}');
|
||||
} else {
|
||||
return Navigator(
|
||||
initialRoute: widget.setupChatRoute,
|
||||
onGenerateRoute: _onGenerateRoute,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Route _onGenerateRoute(RouteSettings settings) {
|
||||
late Widget page;
|
||||
switch (settings.name) {
|
||||
case routeChatChannels:
|
||||
page = ChannelListPage(
|
||||
client: client,
|
||||
);
|
||||
break;
|
||||
case routeChatChannel:
|
||||
final channel = settings.arguments as Channel;
|
||||
page = StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw Exception('Unknown route: ${settings.name}');
|
||||
}
|
||||
return MaterialPageRoute<dynamic>(
|
||||
builder: (context) {
|
||||
return page;
|
||||
},
|
||||
settings: settings,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This **ChatSetup** widget is similar to what it was in the first option, the only difference is that we’re also introducing a nested navigator and handling those nested routes.
|
||||
|
||||
The steps for the above code are:
|
||||
|
||||
1. Create a **StreamChatClient** instance
|
||||
2. Call `connectUser` within `initState` and await the result using a **FutureBuilder**
|
||||
3. Introduce a **StreamChat** widget into the widget tree
|
||||
4. Introduce a new **Navigator** for the chat-specific routes
|
||||
5. Call `disconnectUser` within `dispose`
|
||||
|
||||
### Displaying Stream Chat UI Widgets and Global Navigation
|
||||
|
||||
Then finally, the **ChannelListPage** could look something like the following:
|
||||
|
||||
```dart
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
const ChannelListPage({
|
||||
super.key,
|
||||
required this.client,
|
||||
});
|
||||
|
||||
final StreamChatClient client;
|
||||
|
||||
@override
|
||||
State<ChannelListPage> createState() => _ChannelListPageState();
|
||||
}
|
||||
|
||||
class _ChannelListPageState extends State<ChannelListPage> {
|
||||
late final _controller = StreamChannelListController(
|
||||
client: widget.client,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _popChatPages() {
|
||||
final nav = context.read<GlobalKey<NavigatorState>>();
|
||||
nav.currentState!.pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
_popChatPages();
|
||||
return true;
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: BackButton(
|
||||
onPressed: () {
|
||||
_popChatPages();
|
||||
},
|
||||
),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _controller.refresh,
|
||||
child: StreamChannelListView(
|
||||
controller: _controller,
|
||||
onChannelTap: (channel) {
|
||||
Navigator.pushNamed(context, routeChatChannel,
|
||||
arguments: channel);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are two important things to note in the above code:
|
||||
|
||||
- We’re accessing the global **NavigatorState** using Provider and calling `pop()` on the back press. This will ensure that this entire route is disposed of and that the Stream connection is closed.
|
||||
- Within `onChannelTap` we’re calling `pushNamed` and passing in the route to display a single channel page. This uses the navigator introduced in **ChatSetup**, which is the closest navigator within the widget tree.
|
||||
|
||||
A few things to take note of:
|
||||
|
||||
- You’ll always need to access the global navigator if you want to dispose of this route. you need to ensure that this route is popped or replaced, otherwise, the Stream connection will remain active for as long as the chat route is on the stack (or manually disconnected).
|
||||
|
||||
## Option 3: Navigator 2.0 - Using GoRouter
|
||||
|
||||
This final example will be a combination of the first two options. The following code is an example of how to initialize Stream Chat in a part of the widget tree using the [GoRouter package](https://pub.dev/packages/go_router). This solution will change depending on how you do routing in your Flutter application and which package (if any) you use.
|
||||
|
||||
### Application Routes and Conditional Stream Initialization
|
||||
|
||||
For this example we have the following routes and subroutes:
|
||||
|
||||
```dart
|
||||
|_ '/' -> home page
|
||||
|_ 'settings/' - settings page
|
||||
|_ 'chat/' - chat home, shows the channels list page
|
||||
|_ 'channel/' - specific channel page
|
||||
```
|
||||
|
||||
In the **MyApp** widget, we’ll initialize **GoRouter** and the **StreamChatClient**.
|
||||
However, we will only expose the **StreamChat** widget for certain routes.
|
||||
Additionally, we’ll create a **ChatSetup** widget that connects and disconnects the user.
|
||||
This widget will also **only** be injected for the `/chat` routes.
|
||||
|
||||
```dart
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
final _client = StreamChatClient(
|
||||
'q29npdvqjr99',
|
||||
logLevel: Level.OFF,
|
||||
);
|
||||
bool wasPreviousRouteChat = false;
|
||||
|
||||
late final _router = GoRouter(
|
||||
initialLocation: '/',
|
||||
navigatorBuilder: (context, state, child) {
|
||||
if (state.location.startsWith('/chat')) {
|
||||
wasPreviousRouteChat = true;
|
||||
return StreamChat(
|
||||
client: _client,
|
||||
child: ChatSetup(client: _client, child: child),
|
||||
);
|
||||
} else {
|
||||
if (wasPreviousRouteChat) {
|
||||
wasPreviousRouteChat = false;
|
||||
return StreamChat(
|
||||
client: _client,
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => const HomeScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'settings',
|
||||
builder: (context, state) => const SettingsPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chat',
|
||||
builder: (context, state) => const ChannelListPage(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'channel',
|
||||
builder: (context, state) {
|
||||
final channel = state.extra as Channel;
|
||||
return StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
routeInformationParser: _router.routeInformationParser,
|
||||
routeInformationProvider: _router.routeInformationProvider,
|
||||
routerDelegate: _router.routerDelegate,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you’re unfamiliar with GoRouter it will help to first read the documentation and then come back to this guide.
|
||||
|
||||
There are a few important things to note in the above code:
|
||||
|
||||
- Define our routes and subroutes
|
||||
- Specify the initial route with `initialLocation`
|
||||
- Use the `navigatorBuilder` to wrap certain routes with **StreamChat** and **ChatSetup**.
|
||||
- The `wasPreviousRouteChat` \***\*boolean is used to determine if the previous route was a chat route. This is important because when you press the back button from the `/chat` route and navigate to the `/` route, the **StreamChat** widget still needs to be accessible while the navigation transition occurs. However, if you then navigate to the `/setting` route, you no longer need the **StreamChat\*\* widget and can safely remove it.
|
||||
|
||||
### Navigating With GoRouter
|
||||
|
||||
Within the **HomeScreen** you can create a button that on press navigates to the `/chat` route:
|
||||
|
||||
```dart
|
||||
GoRouter.of(context).go('/chat');
|
||||
```
|
||||
|
||||
### Connecting and Disconnecting Users
|
||||
|
||||
Same as before, we’ll use the **ChatSetup** widget to connect and disconnect a user.
|
||||
This time the widget also takes in a **child** widget to display once the connection is finished.
|
||||
The child widget is dependent on the route we’re navigating to.
|
||||
|
||||
```dart
|
||||
class ChatSetup extends StatefulWidget {
|
||||
const ChatSetup({
|
||||
Key? key,
|
||||
required this.client,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
final StreamChatClient client;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<ChatSetup> createState() => _ChatSetupState();
|
||||
}
|
||||
|
||||
class _ChatSetupState extends State<ChatSetup> {
|
||||
late final Future<OwnUser> connectionFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
connectionFuture = widget.client.connectUser(
|
||||
User(id: 'USER_ID'),
|
||||
'TOKEN',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.client.disconnectUser();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: FutureBuilder(
|
||||
future: connectionFuture,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
switch (snapshot.connectionState) {
|
||||
case ConnectionState.waiting:
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
default:
|
||||
if (snapshot.hasError) {
|
||||
return Text('Error: ${snapshot.error}');
|
||||
} else {
|
||||
return widget.child;
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will connect the Stream chat user within `initState` and disconnect the user on `dispose`. This is similar to the previous examples we explored.
|
||||
|
||||
### Displaying the Stream UI Widgets
|
||||
|
||||
The **ChannelListPage** can look something like the following:
|
||||
|
||||
```dart
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
const ChannelListPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ChannelListPage> createState() => _ChannelListPageState();
|
||||
}
|
||||
|
||||
class _ChannelListPageState extends State<ChannelListPage> {
|
||||
late final client = StreamChat.of(context).client;
|
||||
late final _controller = StreamChannelListController(
|
||||
client: client,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _controller.refresh,
|
||||
child: StreamChannelListView(
|
||||
controller: _controller,
|
||||
onChannelTap: (channel) {
|
||||
GoRouter.of(context).go('/chat/channel', extra: channel);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is the same as before, the only difference is that the navigation is slightly different; now we’re navigating to the `/chat/channel` route for individual channel pages.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This guide demonstrated three different ways to initialize Stream Chat in a part of the Flutter widget tree.
|
||||
|
||||
There are two key takeaways
|
||||
|
||||
1. Accessing **StreamChat** depends on your location in the widget tree
|
||||
2. How you ultimately decide to expose **StreamChat** and connect users will be up to your application architecture and how you manage routing.
|
||||
|
||||
The above are only examples that can be refined and tweaked to suit your needs.
|
||||
@@ -27,7 +27,6 @@ class UserListPageState extends State<UserListPage> {
|
||||
/// [StreamUserListController].
|
||||
late final userListController = StreamUserListController(
|
||||
client: StreamChatCore.of(context).client,
|
||||
filter: Filter.notEqual('id', StreamChatCore.of(context).currentUser!),
|
||||
);
|
||||
```
|
||||
|
||||
@@ -110,4 +109,4 @@ Widget build(BuildContext context) => Scaffold(
|
||||
);
|
||||
```
|
||||
|
||||
In this case we're using the [`LazyLoadScrollView`](./lazy_load_scroll_view.mdx) widget to load more data when the user scrolls to the bottom of the list.
|
||||
In this case we're using the [`LazyLoadScrollView`](./lazy_load_scroll_view.mdx) widget to load more data when the user scrolls to the bottom of the list.
|
||||
+1
-1
@@ -128,7 +128,7 @@ If you're not sure if you've set up push notifications correctly (e.g. you don't
|
||||
11. After configuring [stream-cli](https://github.com/GetStream/stream-cli) paste the following command on command line using your user ID
|
||||
|
||||
```shell
|
||||
stream-cli chat test-push --user-id <USER-ID>
|
||||
stream chat:push:test -u <USER-ID>
|
||||
```
|
||||
|
||||
You should get a test push notification
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@ If you're not sure whether you've set up push notifications correctly, for examp
|
||||
9. Tap on `Device ID` and copy it
|
||||
11. After configuring [stream-cli](https://github.com/GetStream/stream-cli), run the following command using your user ID:
|
||||
```shell
|
||||
stream-cli chat test-push --user-id <USER-ID>
|
||||
stream chat:push:test -u <USER-ID>
|
||||
```
|
||||
|
||||
You should get a test push notification 🥳
|
||||
|
||||
-10
@@ -1,9 +1,6 @@
|
||||
name: stream_chat_flutter
|
||||
repository: https://github.com/GetStream/stream-chat-flutter
|
||||
|
||||
versioning:
|
||||
mode: independent
|
||||
|
||||
packages:
|
||||
- packages/**
|
||||
|
||||
@@ -97,10 +94,3 @@ scripts:
|
||||
npm install -g https://github.com/GetStream/stream-chat-docusaurus-cli &&
|
||||
npx stream-chat-docusaurus -i -s
|
||||
description: Runs the docusaurus documentation locally.
|
||||
|
||||
dev_dependencies:
|
||||
dart_code_metrics: ^4.4.0
|
||||
|
||||
environment:
|
||||
sdk: '>=2.17.0 <3.0.0'
|
||||
flutter: '>=1.17.0 <3.0.0'
|
||||
@@ -1,3 +1,35 @@
|
||||
## 5.0.0
|
||||
|
||||
- Included the changes from version [4.5.0](#450).
|
||||
|
||||
🛑️ Breaking Changes from `5.0.0-beta.2`
|
||||
|
||||
- `Channel.addMembers`, `Channel.removeMembers`, `Channel.inviteMembers` and `Channel.update`
|
||||
positional params are now optional params.
|
||||
|
||||
```dart
|
||||
// previous
|
||||
channel.addMembers([...ids], message, hideHistory);
|
||||
channel.removeMembers([...ids], message);
|
||||
channel.inviteMembers([...ids], message);
|
||||
channel.update({...channelData}, updateMessage);
|
||||
|
||||
// new
|
||||
channel.addMembers([...ids], message: ..., hideHistory: ...);
|
||||
channel.removeMembers([...ids], message: ...);
|
||||
channel.inviteMembers([...ids], message: ...);
|
||||
channel.update({...channelData}, updateMessage: ...);
|
||||
```
|
||||
|
||||
## 5.0.0-beta.2
|
||||
|
||||
- Included the changes from version [4.4.0](#440) and [4.4.1](#441).
|
||||
|
||||
## 5.0.0-beta.1
|
||||
|
||||
- Minor fixes.
|
||||
- Removed deprecated code.
|
||||
|
||||
## 4.6.0
|
||||
|
||||
✅ Added
|
||||
|
||||
@@ -4,7 +4,10 @@ import 'package:stream_chat/stream_chat.dart';
|
||||
Future<void> main() async {
|
||||
/// Create a new instance of [StreamChatClient]
|
||||
/// by passing the apikey obtained from your project dashboard.
|
||||
final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO);
|
||||
final client = StreamChatClient(
|
||||
'b67pax5b2wdq',
|
||||
logLevel: Level.INFO,
|
||||
);
|
||||
|
||||
/// Set the current user. In a production scenario, this should be done using
|
||||
/// a backend to generate a user token using our server SDK.
|
||||
|
||||
@@ -1064,9 +1064,9 @@ class Channel {
|
||||
/// See, https://getstream.io/chat/docs/other-rest/channel_update/?language=dart
|
||||
/// for more information.
|
||||
Future<UpdateChannelResponse> update(
|
||||
Map<String, Object?> channelData, [
|
||||
Map<String, Object?> channelData, {
|
||||
Message? updateMessage,
|
||||
]) async {
|
||||
}) async {
|
||||
_checkInitialized();
|
||||
return _client.updateChannel(
|
||||
id!,
|
||||
@@ -1149,12 +1149,10 @@ class Channel {
|
||||
|
||||
/// Add members to the channel.
|
||||
Future<AddMembersResponse> addMembers(
|
||||
List<String> memberIds, [
|
||||
List<String> memberIds, {
|
||||
Message? message,
|
||||
// TODO: Convert to optional parameters in v5.0.0
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
bool hideHistory = false,
|
||||
]) async {
|
||||
}) async {
|
||||
_checkInitialized();
|
||||
return _client.addChannelMembers(
|
||||
id!,
|
||||
@@ -1167,18 +1165,18 @@ class Channel {
|
||||
|
||||
/// Invite members to the channel.
|
||||
Future<InviteMembersResponse> inviteMembers(
|
||||
List<String> memberIds, [
|
||||
List<String> memberIds, {
|
||||
Message? message,
|
||||
]) async {
|
||||
}) async {
|
||||
_checkInitialized();
|
||||
return _client.inviteChannelMembers(id!, type, memberIds, message: message);
|
||||
}
|
||||
|
||||
/// Remove members from the channel.
|
||||
Future<RemoveMembersResponse> removeMembers(
|
||||
List<String> memberIds, [
|
||||
List<String> memberIds, {
|
||||
Message? message,
|
||||
]) async {
|
||||
}) async {
|
||||
_checkInitialized();
|
||||
return _client.removeChannelMembers(id!, type, memberIds, message: message);
|
||||
}
|
||||
@@ -2086,10 +2084,6 @@ class ChannelClientState {
|
||||
(m) => m.user?.id == _channel.client.state.currentUser?.id,
|
||||
);
|
||||
|
||||
/// User role for the current user.
|
||||
@Deprecated('Please use currentUserChannelRole')
|
||||
String? get currentUserRole => currentUserMember?.role;
|
||||
|
||||
/// Channel role for the current user
|
||||
String? get currentUserChannelRole => currentUserMember?.channelRole;
|
||||
|
||||
|
||||
@@ -70,15 +70,9 @@ class StreamChatClient {
|
||||
Duration receiveTimeout = const Duration(seconds: 6),
|
||||
StreamChatApi? chatApi,
|
||||
WebSocket? ws,
|
||||
@Deprecated('Use [attachmentFileUploaderProvider] instead')
|
||||
AttachmentFileUploader? attachmentFileUploader,
|
||||
AttachmentFileUploaderProvider? attachmentFileUploaderProvider,
|
||||
}) : assert(
|
||||
attachmentFileUploader == null ||
|
||||
attachmentFileUploaderProvider == null,
|
||||
'You can only use one of [attachmentFileUploader] '
|
||||
'or [attachmentFileUploaderProvider]',
|
||||
) {
|
||||
AttachmentFileUploaderProvider attachmentFileUploaderProvider =
|
||||
StreamAttachmentFileUploader.new,
|
||||
}) {
|
||||
logger.info('Initiating new StreamChatClient');
|
||||
|
||||
final options = StreamHttpClientOptions(
|
||||
@@ -88,23 +82,13 @@ class StreamChatClient {
|
||||
headers: {'X-Stream-Client': defaultUserAgent},
|
||||
);
|
||||
|
||||
// TODO: simplify this once we remove the deprecated field.
|
||||
final AttachmentFileUploaderProvider fileUploaderProvider;
|
||||
if (attachmentFileUploaderProvider != null) {
|
||||
fileUploaderProvider = attachmentFileUploaderProvider;
|
||||
} else if (attachmentFileUploader != null) {
|
||||
fileUploaderProvider = (httpClient) => attachmentFileUploader;
|
||||
} else {
|
||||
fileUploaderProvider = StreamAttachmentFileUploader.new;
|
||||
}
|
||||
|
||||
_chatApi = chatApi ??
|
||||
StreamChatApi(
|
||||
apiKey,
|
||||
options: options,
|
||||
tokenManager: _tokenManager,
|
||||
connectionIdManager: _connectionIdManager,
|
||||
attachmentFileUploaderProvider: fileUploaderProvider,
|
||||
attachmentFileUploaderProvider: attachmentFileUploaderProvider,
|
||||
logger: detachedLogger('🕸️'),
|
||||
);
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ class KeyStrokeHandler {
|
||||
// This is needed to prevent the user from being stuck in typing mode.
|
||||
if (_lastTypingEvent != null) {
|
||||
// We don't need to handle the error here
|
||||
// ignore: no-empty-block
|
||||
_stopTyping(_currentParentId).catchError((_) {});
|
||||
}
|
||||
_cancelKeyStrokeTimer();
|
||||
|
||||
@@ -62,8 +62,6 @@ class PaginationParams extends Equatable {
|
||||
/// ```
|
||||
const PaginationParams({
|
||||
this.limit = 10,
|
||||
this.before = 10,
|
||||
this.after = 10,
|
||||
this.offset,
|
||||
this.next,
|
||||
this.idAround,
|
||||
@@ -88,14 +86,6 @@ class PaginationParams extends Equatable {
|
||||
/// The amount of items requested from the APIs.
|
||||
final int limit;
|
||||
|
||||
/// The amount of items requested before message ID from the APIs.
|
||||
@Deprecated('before is deprecated, use limit instead')
|
||||
final int before;
|
||||
|
||||
/// The amount of items requested after message ID from the APIs.
|
||||
@Deprecated('after is deprecated, use limit instead')
|
||||
final int after;
|
||||
|
||||
/// The offset of requesting items.
|
||||
final int? offset;
|
||||
|
||||
@@ -165,8 +155,6 @@ class PaginationParams extends Equatable {
|
||||
}) =>
|
||||
PaginationParams(
|
||||
limit: limit ?? this.limit,
|
||||
before: before ?? this.before,
|
||||
after: limit ?? this.after,
|
||||
offset: offset ?? this.offset,
|
||||
idAround: idAround ?? this.idAround,
|
||||
next: next ?? this.next,
|
||||
@@ -186,8 +174,6 @@ class PaginationParams extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
limit,
|
||||
before,
|
||||
after,
|
||||
offset,
|
||||
next,
|
||||
idAround,
|
||||
|
||||
@@ -21,8 +21,6 @@ Map<String, dynamic> _$SortOptionToJson<T>(SortOption<T> instance) =>
|
||||
PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) =>
|
||||
PaginationParams(
|
||||
limit: json['limit'] as int? ?? 10,
|
||||
before: json['before'] as int? ?? 10,
|
||||
after: json['after'] as int? ?? 10,
|
||||
offset: json['offset'] as int?,
|
||||
next: json['next'] as String?,
|
||||
idAround: json['id_around'] as String?,
|
||||
@@ -50,8 +48,6 @@ PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) =>
|
||||
Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
|
||||
final val = <String, dynamic>{
|
||||
'limit': instance.limit,
|
||||
'before': instance.before,
|
||||
'after': instance.after,
|
||||
};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
|
||||
@@ -23,10 +23,10 @@ class StreamChatApi {
|
||||
StreamHttpClientOptions? options,
|
||||
TokenManager? tokenManager,
|
||||
ConnectionIdManager? connectionIdManager,
|
||||
AttachmentFileUploaderProvider? attachmentFileUploaderProvider,
|
||||
AttachmentFileUploaderProvider attachmentFileUploaderProvider =
|
||||
StreamAttachmentFileUploader.new,
|
||||
Logger? logger,
|
||||
}) : _fileUploaderProvider =
|
||||
attachmentFileUploaderProvider ?? StreamAttachmentFileUploader.new,
|
||||
}) : _fileUploaderProvider = attachmentFileUploaderProvider,
|
||||
_client = client ??
|
||||
StreamHttpClient(
|
||||
apiKey,
|
||||
|
||||
@@ -49,7 +49,7 @@ class Attachment extends Equatable {
|
||||
if (file?.mimeType != null) 'mime_type': file?.mimeType?.mimeType,
|
||||
} {
|
||||
this.uploadState = uploadState ??
|
||||
((assetUrl != null || imageUrl != null)
|
||||
((assetUrl != null || imageUrl != null || thumbUrl != null)
|
||||
? const UploadState.success()
|
||||
: const UploadState.preparing());
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ class AttachmentFile {
|
||||
final String? _name;
|
||||
|
||||
/// File name including its extension.
|
||||
String? get name => _name ?? path?.split('/').last;
|
||||
String? get name =>
|
||||
_name ?? path?.split(CurrentPlatform.isWindows ? r'\' : '/').last;
|
||||
|
||||
/// Byte data for this file. Particularly useful if you want to manipulate
|
||||
/// its data or easily upload to somewhere else.
|
||||
@@ -85,6 +86,22 @@ class AttachmentFile {
|
||||
}
|
||||
return multiPartFile;
|
||||
}
|
||||
|
||||
/// Creates a copy of this [AttachmentFile] but with the given fields
|
||||
/// replaced with the new values.
|
||||
AttachmentFile copyWith({
|
||||
String? path,
|
||||
String? name,
|
||||
Uint8List? bytes,
|
||||
int? size,
|
||||
}) {
|
||||
return AttachmentFile(
|
||||
path: path ?? this.path,
|
||||
name: name ?? this.name,
|
||||
bytes: bytes ?? this.bytes,
|
||||
size: size ?? this.size,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Union class to hold various [UploadState] of a attachment.
|
||||
|
||||
@@ -39,7 +39,6 @@ class ChannelModel {
|
||||
createdAt = createdAt ?? DateTime.now(),
|
||||
updatedAt = updatedAt ?? DateTime.now(),
|
||||
|
||||
// TODO: Make them top-level fields in v5
|
||||
// For backwards compatibility, set 'disabled', 'hidden'
|
||||
// and 'truncated_at' in [extraData].
|
||||
extraData = {
|
||||
|
||||
@@ -16,7 +16,6 @@ class Member extends Equatable {
|
||||
this.inviteAcceptedAt,
|
||||
this.inviteRejectedAt,
|
||||
this.invited = false,
|
||||
this.role,
|
||||
this.channelRole,
|
||||
this.userId,
|
||||
this.isModerator = false,
|
||||
@@ -48,10 +47,6 @@ class Member extends Equatable {
|
||||
/// True if the user has been invited to the channel
|
||||
final bool invited;
|
||||
|
||||
/// The role of the user in the channel
|
||||
@Deprecated('Please use channelRole')
|
||||
final String? role;
|
||||
|
||||
/// The role of this member in the channel
|
||||
final String? channelRole;
|
||||
|
||||
@@ -100,7 +95,6 @@ class Member extends Equatable {
|
||||
banned: banned ?? this.banned,
|
||||
banExpires: banExpires ?? this.banExpires,
|
||||
shadowBanned: shadowBanned ?? this.shadowBanned,
|
||||
role: role ?? this.role,
|
||||
channelRole: channelRole ?? this.channelRole,
|
||||
userId: userId ?? this.userId,
|
||||
isModerator: isModerator ?? this.isModerator,
|
||||
@@ -117,7 +111,6 @@ class Member extends Equatable {
|
||||
inviteAcceptedAt,
|
||||
inviteRejectedAt,
|
||||
invited,
|
||||
role,
|
||||
channelRole,
|
||||
userId,
|
||||
isModerator,
|
||||
|
||||
@@ -17,7 +17,6 @@ Member _$MemberFromJson(Map<String, dynamic> json) => Member(
|
||||
? null
|
||||
: DateTime.parse(json['invite_rejected_at'] as String),
|
||||
invited: json['invited'] as bool? ?? false,
|
||||
role: json['role'] as String?,
|
||||
channelRole: json['channel_role'] as String?,
|
||||
userId: json['user_id'] as String?,
|
||||
isModerator: json['is_moderator'] as bool? ?? false,
|
||||
@@ -39,7 +38,6 @@ Map<String, dynamic> _$MemberToJson(Member instance) => <String, dynamic>{
|
||||
'invite_accepted_at': instance.inviteAcceptedAt?.toIso8601String(),
|
||||
'invite_rejected_at': instance.inviteRejectedAt?.toIso8601String(),
|
||||
'invited': instance.invited,
|
||||
'role': instance.role,
|
||||
'channel_role': instance.channelRole,
|
||||
'user_id': instance.userId,
|
||||
'is_moderator': instance.isModerator,
|
||||
|
||||
@@ -46,7 +46,6 @@ class User extends Equatable {
|
||||
this.language,
|
||||
}) : createdAt = createdAt ?? DateTime.now(),
|
||||
updatedAt = updatedAt ?? DateTime.now(),
|
||||
// TODO: Make them top-level fields in v5
|
||||
// For backwards compatibility, set 'name', 'image' in [extraData].
|
||||
extraData = {
|
||||
...extraData,
|
||||
|
||||
@@ -4,25 +4,25 @@ import 'package:stream_chat/src/core/platform_detector/platform_detector_stub.da
|
||||
|
||||
/// Possible platforms
|
||||
enum PlatformType {
|
||||
///
|
||||
/// Android: <https://www.android.com/>
|
||||
android,
|
||||
|
||||
///
|
||||
/// iOS: <https://www.apple.com/ios/>
|
||||
ios,
|
||||
|
||||
///
|
||||
/// web: <https://en.wikipedia.org/wiki/World_Wide_Web>
|
||||
web,
|
||||
|
||||
///
|
||||
/// macOS: <https://www.apple.com/macos>
|
||||
macOS,
|
||||
|
||||
///
|
||||
/// Windows: <https://www.windows.com>
|
||||
windows,
|
||||
|
||||
///
|
||||
/// Linux: <https://www.linux.org>
|
||||
linux,
|
||||
|
||||
///
|
||||
/// Fuchsia: <https://fuchsia.dev/fuchsia-src/concepts>
|
||||
fuchsia,
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ class CurrentPlatform {
|
||||
/// True if the app is running on fuchsia
|
||||
static bool get isFuchsia => type == PlatformType.fuchsia;
|
||||
|
||||
/// True if the app is running in test environment
|
||||
static bool get isFlutterTest => isFlutterTestEnvironment;
|
||||
|
||||
/// Returns a string version of the platform
|
||||
static String get name {
|
||||
switch (type) {
|
||||
@@ -68,8 +71,6 @@ class CurrentPlatform {
|
||||
return 'linux';
|
||||
case PlatformType.fuchsia:
|
||||
return 'fuchsia';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,3 +10,8 @@ PlatformType get currentPlatform {
|
||||
if (Platform.isIOS) return PlatformType.ios;
|
||||
return PlatformType.android;
|
||||
}
|
||||
|
||||
/// True if the app is running in test environment.
|
||||
bool get isFlutterTestEnvironment {
|
||||
return Platform.environment.containsKey('FLUTTER_TEST');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
|
||||
|
||||
/// Stub implementation
|
||||
PlatformType get currentPlatform {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
PlatformType get currentPlatform => throw UnimplementedError();
|
||||
|
||||
/// Stub implementation
|
||||
bool get isFlutterTestEnvironment => throw UnimplementedError();
|
||||
|
||||
@@ -2,3 +2,8 @@ import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
|
||||
|
||||
/// Version running on web
|
||||
PlatformType get currentPlatform => PlatformType.web;
|
||||
|
||||
/// True if the app is running in test environment.
|
||||
///
|
||||
/// Always returns false as we don't have environment variables on web.
|
||||
bool get isFlutterTestEnvironment => false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:stream_chat/src/core/api/requests.dart';
|
||||
import 'package:stream_chat/src/core/models/attachment_file.dart';
|
||||
import 'package:stream_chat/src/core/models/channel_model.dart';
|
||||
import 'package:stream_chat/src/core/models/channel_state.dart';
|
||||
import 'package:stream_chat/src/core/models/event.dart';
|
||||
@@ -8,6 +9,7 @@ import 'package:stream_chat/src/core/models/message.dart';
|
||||
import 'package:stream_chat/src/core/models/reaction.dart';
|
||||
import 'package:stream_chat/src/core/models/read.dart';
|
||||
import 'package:stream_chat/src/core/models/user.dart';
|
||||
import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
|
||||
import 'package:stream_chat/src/core/util/extension.dart';
|
||||
|
||||
/// A simple client used for persisting chat data locally.
|
||||
@@ -133,7 +135,7 @@ abstract class ChatPersistenceClient {
|
||||
/// Remove a pinned message by message [cids]
|
||||
Future<void> deletePinnedMessageByCids(List<String> cids);
|
||||
|
||||
/// Remove a channel by [cid]
|
||||
/// Remove a channel by [channelId]
|
||||
Future<void> deleteChannels(List<String> cids);
|
||||
|
||||
/// Updates the message data of a particular channel [cid] with
|
||||
@@ -243,7 +245,15 @@ abstract class ChatPersistenceClient {
|
||||
final cid = channel.cid;
|
||||
final reads = state.read;
|
||||
final members = state.members;
|
||||
final messages = state.messages;
|
||||
final Iterable<Message>? messages;
|
||||
if (CurrentPlatform.isWeb) {
|
||||
messages = state.messages?.where((it) => !it.attachments.any(
|
||||
(attachment) =>
|
||||
attachment.uploadState != const UploadState.success(),
|
||||
));
|
||||
} else {
|
||||
messages = state.messages;
|
||||
}
|
||||
final pinnedMessages = state.pinnedMessages;
|
||||
|
||||
// Preparing deletion data
|
||||
@@ -255,7 +265,7 @@ abstract class ChatPersistenceClient {
|
||||
// preparing addition data
|
||||
channelWithReads[cid] = reads;
|
||||
channelWithMembers[cid] = members;
|
||||
channelWithMessages[cid] = messages;
|
||||
channelWithMessages[cid] = messages?.toList();
|
||||
channelWithPinnedMessages[cid] = pinnedMessages;
|
||||
|
||||
reactions.addAll(messages?.expand(_expandReactions) ?? []);
|
||||
|
||||
@@ -5,12 +5,14 @@ export 'package:dio/src/cancel_token.dart';
|
||||
export 'package:dio/src/dio_error.dart';
|
||||
export 'package:dio/src/multipart_file.dart';
|
||||
export 'package:dio/src/options.dart';
|
||||
export 'package:dio/src/options.dart' show ProgressCallback;
|
||||
export 'package:logging/logging.dart' show Logger, Level, LogRecord;
|
||||
export 'package:rate_limiter/rate_limiter.dart';
|
||||
export 'package:uuid/uuid.dart';
|
||||
|
||||
export './src/core/api/attachment_file_uploader.dart';
|
||||
export './src/core/api/requests.dart';
|
||||
export './src/core/api/requests.dart';
|
||||
export './src/core/api/responses.dart';
|
||||
export './src/core/api/stream_chat_api.dart' show PushProvider;
|
||||
export './src/core/error/error.dart';
|
||||
@@ -41,6 +43,7 @@ export 'src/client/client.dart';
|
||||
export 'src/client/key_stroke_handler.dart';
|
||||
export 'src/core/api/attachment_file_uploader.dart' show AttachmentFileUploader;
|
||||
export 'src/core/api/requests.dart';
|
||||
export 'src/core/api/requests.dart';
|
||||
export 'src/core/api/responses.dart';
|
||||
export 'src/core/api/stream_chat_api.dart';
|
||||
export 'src/core/error/error.dart';
|
||||
@@ -61,6 +64,7 @@ export 'src/core/models/own_user.dart';
|
||||
export 'src/core/models/reaction.dart';
|
||||
export 'src/core/models/read.dart';
|
||||
export 'src/core/models/user.dart';
|
||||
export 'src/core/platform_detector/platform_detector.dart';
|
||||
export 'src/core/util/extension.dart';
|
||||
export 'src/db/chat_persistence_client.dart';
|
||||
export 'src/event_type.dart';
|
||||
|
||||
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart';
|
||||
/// Current package version
|
||||
/// Used in [StreamChatClient] to build the `x-stream-client` header
|
||||
// ignore: constant_identifier_names
|
||||
const PACKAGE_VERSION = '4.6.0';
|
||||
const PACKAGE_VERSION = '5.0.0';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: stream_chat
|
||||
homepage: https://getstream.io/
|
||||
description: The official Dart client for Stream Chat, a service for building chat applications.
|
||||
version: 4.6.0
|
||||
version: 5.0.0
|
||||
repository: https://github.com/GetStream/stream-chat-flutter
|
||||
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"size": 12,
|
||||
"path": "/me/user/test.jpg",
|
||||
"name": "test.jpg"
|
||||
}
|
||||
@@ -108,7 +108,6 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO : test all persistence related logic in this group
|
||||
group('Initialized Channel with Persistence', () {
|
||||
late final client = MockStreamChatClientWithPersistence();
|
||||
const channelId = 'test-channel-id';
|
||||
@@ -1832,7 +1831,10 @@ void main() {
|
||||
..message = updateMessage,
|
||||
);
|
||||
|
||||
final res = await channel.update(channelData, updateMessage);
|
||||
final res = await channel.update(
|
||||
channelData,
|
||||
updateMessage: updateMessage,
|
||||
);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.channel.cid, channelModel.cid);
|
||||
@@ -2028,7 +2030,7 @@ void main() {
|
||||
..message = message,
|
||||
);
|
||||
|
||||
final res = await channel.addMembers(memberIds, message);
|
||||
final res = await channel.addMembers(memberIds, message: message);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.channel.cid, channelModel.cid);
|
||||
@@ -2060,7 +2062,7 @@ void main() {
|
||||
..message = message,
|
||||
);
|
||||
|
||||
final res = await channel.inviteMembers(memberIds, message);
|
||||
final res = await channel.inviteMembers(memberIds, message: message);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.channel.cid, channelModel.cid);
|
||||
@@ -2093,7 +2095,7 @@ void main() {
|
||||
..message = message,
|
||||
);
|
||||
|
||||
final res = await channel.removeMembers(memberIds, message);
|
||||
final res = await channel.removeMembers(memberIds, message: message);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.channel.cid, channelModel.cid);
|
||||
|
||||
@@ -68,6 +68,4 @@ void main() {
|
||||
expect(retryQueue.hasMessages, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Add more tests once macbook is fixed :(
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:stream_chat/src/core/models/attachment_file.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../utils.dart';
|
||||
|
||||
void main() {
|
||||
group('src/models/attachment_file', () {
|
||||
test('should parse json correctly', () {
|
||||
final attachment =
|
||||
AttachmentFile.fromJson(jsonFixture('attachment_file.json'));
|
||||
expect(attachment.name, 'test.jpg');
|
||||
expect(attachment.size, 12);
|
||||
expect(
|
||||
attachment.path,
|
||||
'/me/user/test.jpg',
|
||||
);
|
||||
});
|
||||
|
||||
test('should serialize to json correctly', () {
|
||||
final attachment = AttachmentFile(
|
||||
size: 12,
|
||||
bytes: Uint8List.fromList([1, 2, 3]),
|
||||
name: 'test.jpg',
|
||||
path: '/me/user/test.jpg',
|
||||
);
|
||||
|
||||
expect(
|
||||
attachment.toJson(),
|
||||
{
|
||||
'size': 12,
|
||||
'name': 'test.jpg',
|
||||
'path': '/me/user/test.jpg',
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,116 @@
|
||||
## 5.0.0
|
||||
|
||||
- Included the changes from version [4.5.0](#450).
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- [[#1326]](https://github.com/GetStream/stream-chat-flutter/issues/1326) Fixed hitting "enter" on
|
||||
the android keyboard sends the message instead of going to a new line.
|
||||
|
||||
✅ Added
|
||||
|
||||
- Added `StreamMemberGridView` and `StreamMemberListView`.
|
||||
- Added support for additional text field params in `StreamMessageInput`
|
||||
* `maxLines`
|
||||
* `minLines`
|
||||
* `textInputAction`
|
||||
* `keyboardType`
|
||||
* `textCapitalization`
|
||||
- Added `showStreamAttachmentPickerModalBottomSheet` to show the attachment picker modal bottom sheet.
|
||||
|
||||
🔄 Changed
|
||||
|
||||
- Removed Emoji picker from `StreamMessageInput`.
|
||||
|
||||
## 5.0.0-beta.2
|
||||
|
||||
- Included the changes from version [4.4.0](#440) and [4.4.1](#441).
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- Fixed the unread message header in the message list view.
|
||||
- Show dialog after clicking on the camera button and permission is denied.
|
||||
- Fix Jiffy initialization.
|
||||
- Fix loading to unread position in `StreamMessageListView`.
|
||||
- Minor fixes and improvements.
|
||||
|
||||
🔄 Changed
|
||||
|
||||
- [[#1125]](https://github.com/GetStream/stream-chat-flutter/issues/1125) `defaultUserImage`
|
||||
, `placeholderUserImage`, `reactionIcons`, and `enforceUniqueReactions` have been refactored out
|
||||
of `StreamChatThemeData` and into the new `StreamChatConfigurationData` class.
|
||||
|
||||
✅ Added
|
||||
|
||||
- Added `StreamAutocomplete` widget for autocomplete triggers in `StreamMessageInput`.
|
||||
- Added `StreamMessageInput.customAutocompleteTriggers` to allow users to define their custom
|
||||
triggers.
|
||||
|
||||
## 5.0.0-beta.1
|
||||
|
||||
- 🎉 Initial support for desktop 🖥️ and web 🧑💻
|
||||
- Right-click context menus for messages and full-screen attachments
|
||||
- Upload and download attachments using the native desktop file system
|
||||
- Press the "enter" key to send a message
|
||||
- If you are quoting a message and have not yet typed any text, you can press the "esc" key to
|
||||
remove the quoted message.
|
||||
- A dedicated "X" button for removing a quoted message with your mouse
|
||||
- Drag and drop attachment files to `StreamMessageInput`
|
||||
- New `StreamMessageInput.draggingBorder` property to customize the border color of the
|
||||
message input when dropping a file.
|
||||
- Message reactions bubbles
|
||||
- Hovering over a message reaction will show the users that have reacted to the message
|
||||
- Desktop attachment sharing UI
|
||||
- Selectable message text
|
||||
- Gallery navigation controls with keyboard shortcuts (left and right arrow keys)
|
||||
- Appropriate message sizing for large screens
|
||||
- Right-click context menu for `StreamMessageListView` items
|
||||
- `StreamMessageListView` items not swipeable on desktop & web
|
||||
- Video support for Windows & Linux through `dart_vlc`
|
||||
- Video support for macOS through `video_player_macos`
|
||||
- Replace bottom sheets with dialogs where appropriate
|
||||
- Other Additions ✅
|
||||
- `onQuotedMessageCleared` to `StreamMessageInput`
|
||||
- `selected` and `selectedTileColor` to `StreamChannelListTile`
|
||||
- `AttachmentUploadStateBuilder.inProgressBuilder` to `AttachmentUploadStateBuilder`
|
||||
- `AttachmentUploadStateBuilder.successBuilder` to `AttachmentUploadStateBuilder`
|
||||
- `AttachmentUploadStateBuilder.failedBuilder` to `AttachmentUploadStateBuilder`
|
||||
- Translations:
|
||||
- `couldNotReadBytesFromFileError`
|
||||
- `downloadLabel`
|
||||
- `toggleMuteUnmuteAction`
|
||||
- `toggleMuteUnmuteGroupQuestion`
|
||||
- `toggleMuteUnmuteGroupText`
|
||||
- `toggleMuteUnmuteUserQuestion`
|
||||
- `toggleMuteUnmuteUserText`
|
||||
- Deprecated `showConfirmationDialog` in favor of `showConfirmationBottomSheet`
|
||||
- Deprecated `showInfoDialog` in favor of `showInfoBottomSheet`
|
||||
- Deprecated `wrapAttachmentWidget` in favor of the `WrapAttachmentWidget` class
|
||||
- Breaking changes 🚧
|
||||
- `StreamImageAttachment.size` has been converted from type `Size` to type `BoxConstraints`
|
||||
- `StreamFileAttachment.size` has been converted from type `Size` to type `BoxConstraints`
|
||||
- `StreamGiphyAttachment.size` has been converted from type `Size` to type `BoxConstraints`
|
||||
- `StreamVideoAttachment.size` has been converted from type `Size` to type `BoxConstraints`
|
||||
- `StreamVideoThumbnailImage.width` and `StreamVideoThumbnailImage.height` have been removed in
|
||||
favor of
|
||||
`StreamVideoThumbnailImage.constraints`
|
||||
- Dependency updates ⬆️
|
||||
- `chewie: ^1.3.0` -> `chewie: ^1.3.4`
|
||||
- `path_provider: ^2.0.1` -> `path_provider: ^2.0.9`
|
||||
- `video_player: ^2.1.0` -> `video_player: ^2.4.5`
|
||||
- Code Improvements 🔧
|
||||
- Extracted many widgets to classes to improve readability, maintainability, and devtools usage.
|
||||
- Organized internal directory structure
|
||||
- Extracted typedefs to their own file
|
||||
- Updated dartdoc documentation
|
||||
- Various code readability improvements
|
||||
|
||||
## 4.6.0
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- [[#1323]](https://github.com/GetStream/stream-chat-flutter/issues/1323): Fix message text hiding because of a [flutter bug](https://github.com/flutter/flutter/issues/110628).
|
||||
- [[#1323]](https://github.com/GetStream/stream-chat-flutter/issues/1323): Fix message text hiding
|
||||
because of a [flutter bug](https://github.com/flutter/flutter/issues/110628).
|
||||
|
||||
## 4.5.0
|
||||
|
||||
|
||||
@@ -60,6 +60,26 @@ We also use [video_player](https://pub.dev/packages/video_player) to reproduce v
|
||||
To pick images from the camera, we use the [image_picker](https://pub.dev/packages/image_picker) plugin.
|
||||
Follow [these instructions](https://pub.dev/packages/image_picker#ios) to check the requirements.
|
||||
|
||||
### Web
|
||||
|
||||
For the web, edit your `index.html` and add the following in the `<body>` tag in order to allow the SDK to override the right-click behaviour:
|
||||
|
||||
```html
|
||||
<body oncontextmenu="return false;">
|
||||
```
|
||||
|
||||
### MacOS
|
||||
|
||||
For MacOS use the [file_selector](https://pub.dev/packages/file_selector#macos) package.
|
||||
Follow [these instructions](https://pub.dev/packages/file_selector#macos) to check the requirements.
|
||||
|
||||
You also need to add the following [entitlement](https://docs.flutter.dev/development/platform-integration/desktop#entitlements-and-the-app-sandbox):
|
||||
|
||||
```xml
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
It may happen that you have some problems building the app.
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
/build/
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
@@ -40,7 +40,7 @@ android {
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.example"
|
||||
minSdkVersion 22
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 31
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||
<application
|
||||
android:name=".Application"
|
||||
android:label="example"
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_localizations/stream_chat_localizations.dart';
|
||||
|
||||
void main() async {
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
/// Create a new instance of [StreamChatClient] passing the apikey obtained
|
||||
/// from your project dashboard.
|
||||
final client = StreamChatClient(
|
||||
's2dxdhpxd94g',
|
||||
logLevel: Level.INFO,
|
||||
logLevel: Level.OFF,
|
||||
);
|
||||
|
||||
/// Set the current user and connect the websocket. In a production
|
||||
@@ -63,50 +66,272 @@ class MyApp extends StatelessWidget {
|
||||
final Channel channel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => MaterialApp(
|
||||
theme: ThemeData.light(),
|
||||
darkTheme: ThemeData.dark(),
|
||||
supportedLocales: const [
|
||||
Locale('en'),
|
||||
Locale('hi'),
|
||||
Locale('fr'),
|
||||
Locale('it'),
|
||||
Locale('es'),
|
||||
],
|
||||
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
|
||||
builder: (context, widget) => StreamChat(
|
||||
client: client,
|
||||
child: widget,
|
||||
),
|
||||
home: StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
);
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData.light(),
|
||||
darkTheme: ThemeData.dark(),
|
||||
// themeMode: ThemeMode.dark,
|
||||
supportedLocales: const [
|
||||
Locale('en'),
|
||||
Locale('hi'),
|
||||
Locale('fr'),
|
||||
Locale('it'),
|
||||
Locale('es'),
|
||||
],
|
||||
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
|
||||
builder: (context, widget) => StreamChat(
|
||||
client: client,
|
||||
child: widget,
|
||||
),
|
||||
home: StreamChannel(
|
||||
channel: channel,
|
||||
child: const ResponsiveChat(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of messages sent in the current channel.
|
||||
///
|
||||
/// This is implemented using [StreamMessageListView],
|
||||
/// a widget that provides query
|
||||
/// functionalities fetching the messages from the api and showing them in a
|
||||
/// listView.
|
||||
class ChannelPage extends StatelessWidget {
|
||||
/// Creates the page that shows the list of messages
|
||||
const ChannelPage({
|
||||
class ResponsiveChat extends StatelessWidget {
|
||||
const ResponsiveChat({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(),
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveBuilder(
|
||||
builder: (context, sizingInformation) {
|
||||
if (sizingInformation.isMobile) {
|
||||
return ChannelListPage(
|
||||
onTap: (c) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return StreamChannel(
|
||||
channel: c,
|
||||
child: ChannelPage(
|
||||
onBackPressed: (context) {
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return const SplitView();
|
||||
},
|
||||
breakpoints: const ScreenBreakpoints(
|
||||
desktop: 550,
|
||||
tablet: 550,
|
||||
watch: 300,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SplitView extends StatefulWidget {
|
||||
const SplitView({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
_SplitViewState createState() => _SplitViewState();
|
||||
}
|
||||
|
||||
class _SplitViewState extends State<SplitView> {
|
||||
Channel? selectedChannel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Flex(
|
||||
direction: Axis.horizontal,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: ChannelListPage(
|
||||
onTap: (channel) {
|
||||
setState(() {
|
||||
selectedChannel = channel;
|
||||
});
|
||||
},
|
||||
selectedChannel: selectedChannel,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: ClipPath(
|
||||
child: Scaffold(
|
||||
body: selectedChannel != null
|
||||
? StreamChannel(
|
||||
key: ValueKey(selectedChannel!.cid),
|
||||
channel: selectedChannel!,
|
||||
child: const ChannelPage(showBackButton: false),
|
||||
)
|
||||
: Center(
|
||||
child: Text(
|
||||
'Pick a channel to show the messages 💬',
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
),
|
||||
StreamMessageInput(attachmentLimit: 3),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
const ChannelListPage({
|
||||
super.key,
|
||||
this.onTap,
|
||||
this.selectedChannel,
|
||||
});
|
||||
|
||||
final void Function(Channel)? onTap;
|
||||
final Channel? selectedChannel;
|
||||
|
||||
@override
|
||||
State<ChannelListPage> createState() => _ChannelListPageState();
|
||||
}
|
||||
|
||||
class _ChannelListPageState extends State<ChannelListPage> {
|
||||
late final _listController = StreamChannelListController(
|
||||
client: StreamChat.of(context).client,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: StreamChannelListView(
|
||||
onChannelTap: widget.onTap,
|
||||
controller: _listController,
|
||||
itemBuilder: (context, channels, index, defaultWidget) {
|
||||
return defaultWidget.copyWith(
|
||||
selected: channels[index] == widget.selectedChannel,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class ChannelPage extends StatefulWidget {
|
||||
const ChannelPage({
|
||||
super.key,
|
||||
this.showBackButton = true,
|
||||
this.onBackPressed,
|
||||
});
|
||||
|
||||
final bool showBackButton;
|
||||
final void Function(BuildContext)? onBackPressed;
|
||||
|
||||
@override
|
||||
State<ChannelPage> createState() => _ChannelPageState();
|
||||
}
|
||||
|
||||
class _ChannelPageState extends State<ChannelPage> {
|
||||
late final messageInputController = StreamMessageInputController();
|
||||
final focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Navigator(
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: StreamChannelHeader(
|
||||
onBackPressed: widget.onBackPressed != null
|
||||
? () {
|
||||
widget.onBackPressed!(context);
|
||||
}
|
||||
: null,
|
||||
showBackButton: widget.showBackButton,
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(
|
||||
onMessageSwiped:
|
||||
(CurrentPlatform.isAndroid || CurrentPlatform.isIos)
|
||||
? reply
|
||||
: null,
|
||||
threadBuilder: (context, parent) {
|
||||
return ThreadPage(
|
||||
parent: parent!,
|
||||
);
|
||||
},
|
||||
messageBuilder:
|
||||
(context, details, messages, defaultWidget) {
|
||||
return defaultWidget.copyWith(
|
||||
onReplyTap: reply,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
StreamMessageInput(
|
||||
onQuotedMessageCleared:
|
||||
messageInputController.clearQuotedMessage,
|
||||
focusNode: focusNode,
|
||||
messageInputController: messageInputController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
void reply(Message message) {
|
||||
messageInputController.quotedMessage = message;
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
focusNode.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadPage extends StatelessWidget {
|
||||
const ThreadPage({
|
||||
super.key,
|
||||
required this.parent,
|
||||
});
|
||||
|
||||
final Message parent;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: StreamThreadHeader(
|
||||
parent: parent,
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(
|
||||
parentMessage: parent,
|
||||
),
|
||||
),
|
||||
StreamMessageInput(
|
||||
messageInputController: StreamMessageInputController(
|
||||
message: Message(parentId: parent.id),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
void main() async {
|
||||
Future<void> main() async {
|
||||
final client = StreamChatClient(
|
||||
's2dxdhpxd94g',
|
||||
logLevel: Level.INFO,
|
||||
@@ -29,13 +29,15 @@ class MyApp extends StatelessWidget {
|
||||
final StreamChatClient client;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => MaterialApp(
|
||||
builder: (context, child) => StreamChat(
|
||||
client: client,
|
||||
child: child,
|
||||
),
|
||||
home: const SplitView(),
|
||||
);
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
builder: (context, child) => StreamChat(
|
||||
client: client,
|
||||
child: child,
|
||||
),
|
||||
home: const SplitView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SplitView extends StatefulWidget {
|
||||
@@ -51,37 +53,39 @@ class _SplitViewState extends State<SplitView> {
|
||||
Channel? selectedChannel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Flex(
|
||||
direction: Axis.horizontal,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: ChannelListPage(
|
||||
onTap: (channel) {
|
||||
setState(() {
|
||||
selectedChannel = channel;
|
||||
});
|
||||
},
|
||||
),
|
||||
Widget build(BuildContext context) {
|
||||
return Flex(
|
||||
direction: Axis.horizontal,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: ChannelListPage(
|
||||
onTap: (channel) {
|
||||
setState(() {
|
||||
selectedChannel = channel;
|
||||
});
|
||||
},
|
||||
),
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: Scaffold(
|
||||
body: selectedChannel != null
|
||||
? StreamChannel(
|
||||
key: ValueKey(selectedChannel!.cid),
|
||||
channel: selectedChannel!,
|
||||
child: const ChannelPage(),
|
||||
)
|
||||
: Center(
|
||||
child: Text(
|
||||
'Pick a channel to show the messages 💬',
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: Scaffold(
|
||||
body: selectedChannel != null
|
||||
? StreamChannel(
|
||||
key: ValueKey(selectedChannel!.cid),
|
||||
channel: selectedChannel!,
|
||||
child: const ChannelPage(),
|
||||
)
|
||||
: Center(
|
||||
child: Text(
|
||||
'Pick a channel to show the messages 💬',
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: prefer_expression_function_bodies
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
@@ -32,7 +31,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// and [StreamMessageInput]
|
||||
///
|
||||
/// If you now run the simulator you will see a single channel UI.
|
||||
void main() async {
|
||||
Future<void> main() async {
|
||||
final client = StreamChatClient(
|
||||
's2dxdhpxd94g',
|
||||
logLevel: Level.INFO,
|
||||
@@ -90,15 +89,18 @@ class ChannelPage extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(),
|
||||
),
|
||||
StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(),
|
||||
),
|
||||
StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: prefer_expression_function_bodies
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
@@ -31,7 +29,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// [StreamChannelListView] handles pagination
|
||||
/// and updates automatically when new channels are created or when a new
|
||||
/// message is added to a channel.
|
||||
void main() async {
|
||||
Future<void> main() async {
|
||||
final client = StreamChatClient(
|
||||
's2dxdhpxd94g',
|
||||
logLevel: Level.INFO,
|
||||
@@ -100,23 +98,25 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _controller.refresh,
|
||||
child: StreamChannelListView(
|
||||
controller: _controller,
|
||||
onChannelTap: (channel) => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _controller.refresh,
|
||||
child: StreamChannelListView(
|
||||
controller: _controller,
|
||||
onChannelTap: (channel) => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelPage extends StatelessWidget {
|
||||
@@ -125,15 +125,17 @@ class ChannelPage extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(),
|
||||
),
|
||||
StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(),
|
||||
),
|
||||
StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: prefer_expression_function_bodies
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: prefer_expression_function_bodies
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
@@ -106,21 +104,23 @@ class ChannelPage extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(
|
||||
threadBuilder: (_, parentMessage) => ThreadPage(
|
||||
parent: parentMessage,
|
||||
),
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(
|
||||
threadBuilder: (_, parentMessage) => ThreadPage(
|
||||
parent: parentMessage,
|
||||
),
|
||||
),
|
||||
const StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
),
|
||||
const StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadPage extends StatelessWidget {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: prefer_expression_function_bodies
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
@@ -86,21 +85,23 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: StreamChannelListView(
|
||||
controller: _listController,
|
||||
onChannelTap: (channel) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: StreamChannelListView(
|
||||
controller: _listController,
|
||||
onChannelTap: (channel) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelPage extends StatelessWidget {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: prefer_expression_function_bodies
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
@@ -126,21 +124,23 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: StreamChannelListView(
|
||||
controller: _listController,
|
||||
onChannelTap: (channel) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: StreamChannelListView(
|
||||
controller: _listController,
|
||||
onChannelTap: (channel) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelPage extends StatelessWidget {
|
||||
@@ -149,21 +149,23 @@ class ChannelPage extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(
|
||||
threadBuilder: (_, parentMessage) => ThreadPage(
|
||||
parent: parentMessage,
|
||||
),
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(
|
||||
threadBuilder: (_, parentMessage) => ThreadPage(
|
||||
parent: parentMessage,
|
||||
),
|
||||
),
|
||||
const StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
),
|
||||
const StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadPage extends StatelessWidget {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
flutter/ephemeral
|
||||
@@ -0,0 +1,116 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
set(BINARY_NAME "example")
|
||||
set(APPLICATION_ID "com.example.example")
|
||||
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||
|
||||
# Root filesystem for cross-building.
|
||||
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endif()
|
||||
|
||||
# Configure build options.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||
endfunction()
|
||||
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# Application build
|
||||
add_executable(${BINARY_NAME}
|
||||
"main.cc"
|
||||
"my_application.cc"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
)
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
# Only the install-generated bundle's copy of the executable will launch
|
||||
# correctly, since the resources must in the right relative locations. To avoid
|
||||
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||
# the default top-level location.
|
||||
set_target_properties(${BINARY_NAME}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||
)
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# By default, "installing" just makes a relocatable bundle in the build
|
||||
# directory.
|
||||
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
# Start with a clean build bundle directory every time.
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||
" COMPONENT Runtime)
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
if(PLUGIN_BUNDLED_LIBRARIES)
|
||||
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
@@ -0,0 +1,87 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
|
||||
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||
# which isn't available in 3.10.
|
||||
function(list_prepend LIST_NAME PREFIX)
|
||||
set(NEW_LIST "")
|
||||
foreach(element ${${LIST_NAME}})
|
||||
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||
endforeach(element)
|
||||
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# === Flutter Library ===
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"fl_basic_message_channel.h"
|
||||
"fl_binary_codec.h"
|
||||
"fl_binary_messenger.h"
|
||||
"fl_dart_project.h"
|
||||
"fl_engine.h"
|
||||
"fl_json_message_codec.h"
|
||||
"fl_json_method_codec.h"
|
||||
"fl_message_codec.h"
|
||||
"fl_method_call.h"
|
||||
"fl_method_channel.h"
|
||||
"fl_method_codec.h"
|
||||
"fl_method_response.h"
|
||||
"fl_plugin_registrar.h"
|
||||
"fl_plugin_registry.h"
|
||||
"fl_standard_message_codec.h"
|
||||
"fl_standard_method_codec.h"
|
||||
"fl_string_codec.h"
|
||||
"fl_value.h"
|
||||
"fl_view.h"
|
||||
"flutter_linux.h"
|
||||
)
|
||||
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||
target_link_libraries(flutter INTERFACE
|
||||
PkgConfig::GTK
|
||||
PkgConfig::GLIB
|
||||
PkgConfig::GIO
|
||||
)
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dart_vlc/dart_vlc_plugin.h>
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) dart_vlc_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DartVlcPlugin");
|
||||
dart_vlc_plugin_register_with_registrar(dart_vlc_registrar);
|
||||
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
|
||||
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
|
||||
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
|
||||
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
|
||||
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
||||
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) window_manager_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
|
||||
window_manager_plugin_register_with_registrar(window_manager_registrar);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
@@ -0,0 +1,29 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dart_vlc
|
||||
desktop_drop
|
||||
screen_retriever
|
||||
sqlite3_flutter_libs
|
||||
url_launcher_linux
|
||||
window_manager
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "my_application.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
g_autoptr(MyApplication) app = my_application_new();
|
||||
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "my_application.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
struct _MyApplication {
|
||||
GtkApplication parent_instance;
|
||||
char** dart_entrypoint_arguments;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
GtkWindow* window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
|
||||
// Use a header bar when running in GNOME as this is the common style used
|
||||
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||
// desktop).
|
||||
// If running on X and not using GNOME then just use a traditional title bar
|
||||
// in case the window manager does more exotic layout, e.g. tiling.
|
||||
// If running on Wayland assume the header bar will work (may need changing
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||
use_header_bar = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
gtk_header_bar_set_title(header_bar, "example");
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
gtk_window_set_title(window, "example");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
}
|
||||
|
||||
// Implements GApplication::local_command_line.
|
||||
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
// Strip out the first argument as it is the binary name.
|
||||
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||
|
||||
g_autoptr(GError) error = nullptr;
|
||||
if (!g_application_register(application, nullptr, &error)) {
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_application_activate(application);
|
||||
*exit_status = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Implements GObject::dispose.
|
||||
static void my_application_dispose(GObject* object) {
|
||||
MyApplication* self = MY_APPLICATION(object);
|
||||
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void my_application_class_init(MyApplicationClass* klass) {
|
||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||
"application-id", APPLICATION_ID,
|
||||
"flags", G_APPLICATION_NON_UNIQUE,
|
||||
nullptr));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||
#define FLUTTER_MY_APPLICATION_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
|
||||
GtkApplication)
|
||||
|
||||
/**
|
||||
* my_application_new:
|
||||
*
|
||||
* Creates a new Flutter-based application.
|
||||
*
|
||||
* Returns: a new #MyApplication.
|
||||
*/
|
||||
MyApplication* my_application_new();
|
||||
|
||||
#endif // FLUTTER_MY_APPLICATION_H_
|
||||
@@ -10,5 +10,9 @@
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -28,5 +28,7 @@
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>In order to access your photo library</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -4,12 +4,12 @@ import FlutterMacOS
|
||||
class MainFlutterWindow: NSWindow {
|
||||
override func awakeFromNib() {
|
||||
let flutterViewController = FlutterViewController.init()
|
||||
|
||||
let windowFrame = self.frame
|
||||
self.contentViewController = flutterViewController
|
||||
self.setFrame(windowFrame, display: true)
|
||||
|
||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||
|
||||
super.awakeFromNib()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,10 @@ dependencies:
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
collection: ^1.15.0
|
||||
cupertino_icons: ^1.0.3
|
||||
cupertino_icons: ^1.0.4
|
||||
flutter:
|
||||
sdk: flutter
|
||||
responsive_builder: ^0.4.2
|
||||
stream_chat_flutter:
|
||||
path: ../
|
||||
stream_chat_localizations:
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -11,7 +11,6 @@
|
||||
Fore more details:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
-->
|
||||
<base href="/">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
@@ -31,7 +30,7 @@
|
||||
<title>example</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
<body oncontextmenu="return false;">
|
||||
<!-- This script installs service_worker.js to provide PWA functionality to
|
||||
application. For more information, see:
|
||||
https://developers.google.com/web/fundamentals/primers/service-workers -->
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
flutter/ephemeral/
|
||||
|
||||
# Visual Studio user-specific files.
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Visual Studio build-related files.
|
||||
x64/
|
||||
x86/
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
@@ -0,0 +1,95 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
set(BINARY_NAME "example")
|
||||
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||
|
||||
# Configure build options.
|
||||
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if(IS_MULTICONFIG)
|
||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
|
||||
CACHE STRING "" FORCE)
|
||||
else()
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
|
||||
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
|
||||
# Use Unicode for all projects.
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_17)
|
||||
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
|
||||
target_compile_options(${TARGET} PRIVATE /EHsc)
|
||||
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
|
||||
endfunction()
|
||||
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# Application build
|
||||
add_subdirectory("runner")
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# Support files are copied into place next to the executable, so that it can
|
||||
# run in place. This is done instead of making a separate bundle (as on Linux)
|
||||
# so that building and running from within Visual Studio will work.
|
||||
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
|
||||
# Make the "install" step default, as it's required to run.
|
||||
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
if(PLUGIN_BUNDLED_LIBRARIES)
|
||||
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
CONFIGURATIONS Profile;Release
|
||||
COMPONENT Runtime)
|
||||
@@ -0,0 +1,103 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
|
||||
|
||||
# === Flutter Library ===
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"flutter_export.h"
|
||||
"flutter_windows.h"
|
||||
"flutter_messenger.h"
|
||||
"flutter_plugin_registrar.h"
|
||||
"flutter_texture_registrar.h"
|
||||
)
|
||||
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Wrapper ===
|
||||
list(APPEND CPP_WRAPPER_SOURCES_CORE
|
||||
"core_implementations.cc"
|
||||
"standard_codec.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
|
||||
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
|
||||
"plugin_registrar.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
|
||||
list(APPEND CPP_WRAPPER_SOURCES_APP
|
||||
"flutter_engine.cc"
|
||||
"flutter_view_controller.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
|
||||
|
||||
# Wrapper sources needed for a plugin.
|
||||
add_library(flutter_wrapper_plugin STATIC
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
)
|
||||
apply_standard_settings(flutter_wrapper_plugin)
|
||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON)
|
||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden)
|
||||
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
|
||||
target_include_directories(flutter_wrapper_plugin PUBLIC
|
||||
"${WRAPPER_ROOT}/include"
|
||||
)
|
||||
add_dependencies(flutter_wrapper_plugin flutter_assemble)
|
||||
|
||||
# Wrapper sources needed for the runner.
|
||||
add_library(flutter_wrapper_app STATIC
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
)
|
||||
apply_standard_settings(flutter_wrapper_app)
|
||||
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
|
||||
target_include_directories(flutter_wrapper_app PUBLIC
|
||||
"${WRAPPER_ROOT}/include"
|
||||
)
|
||||
add_dependencies(flutter_wrapper_app flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
|
||||
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
${PHONY_OUTPUT}
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
|
||||
windows-x64 $<CONFIG>
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
|
||||
#include <dart_vlc/dart_vlc_plugin.h>
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <flutter_native_view/flutter_native_view_plugin.h>
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <thumblr_windows/thumblr_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
DartVlcPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DartVlcPlugin"));
|
||||
DesktopDropPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FlutterNativeViewPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterNativeViewPlugin"));
|
||||
ScreenRetrieverPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
||||
Sqlite3FlutterLibsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
|
||||
ThumblrWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ThumblrWindowsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
WindowManagerPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter/plugin_registry.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
@@ -0,0 +1,33 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
connectivity_plus_windows
|
||||
dart_vlc
|
||||
desktop_drop
|
||||
file_selector_windows
|
||||
flutter_native_view
|
||||
screen_retriever
|
||||
sqlite3_flutter_libs
|
||||
thumblr_windows
|
||||
url_launcher_windows
|
||||
window_manager
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
@@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
add_executable(${BINARY_NAME} WIN32
|
||||
"flutter_window.cpp"
|
||||
"main.cpp"
|
||||
"utils.cpp"
|
||||
"win32_window.cpp"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
"Runner.rc"
|
||||
"runner.exe.manifest"
|
||||
)
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
|
||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
@@ -0,0 +1,121 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#pragma code_page(65001)
|
||||
#include "resource.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "winres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""winres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Icon
|
||||
//
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
IDI_APP_ICON ICON "resources\\app_icon.ico"
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
#ifdef FLUTTER_BUILD_NUMBER
|
||||
#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
|
||||
#else
|
||||
#define VERSION_AS_NUMBER 1,0,0
|
||||
#endif
|
||||
|
||||
#ifdef FLUTTER_BUILD_NAME
|
||||
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
|
||||
#else
|
||||
#define VERSION_AS_STRING "1.0.0"
|
||||
#endif
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VERSION_AS_NUMBER
|
||||
PRODUCTVERSION VERSION_AS_NUMBER
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904e4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "com.example" "\0"
|
||||
VALUE "FileDescription", "example" "\0"
|
||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||
VALUE "InternalName", "example" "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0"
|
||||
VALUE "OriginalFilename", "example.exe" "\0"
|
||||
VALUE "ProductName", "example" "\0"
|
||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "flutter_window.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
||||
: project_(project) {}
|
||||
|
||||
FlutterWindow::~FlutterWindow() {}
|
||||
|
||||
bool FlutterWindow::OnCreate() {
|
||||
if (!Win32Window::OnCreate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
// The size here must match the window dimensions to avoid unnecessary surface
|
||||
// creation / destruction in the startup path.
|
||||
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
|
||||
frame.right - frame.left, frame.bottom - frame.top, project_);
|
||||
// Ensure that basic setup of the controller was successful.
|
||||
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
|
||||
return false;
|
||||
}
|
||||
RegisterPlugins(flutter_controller_->engine());
|
||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||
return true;
|
||||
}
|
||||
|
||||
void FlutterWindow::OnDestroy() {
|
||||
if (flutter_controller_) {
|
||||
flutter_controller_ = nullptr;
|
||||
}
|
||||
|
||||
Win32Window::OnDestroy();
|
||||
}
|
||||
|
||||
LRESULT
|
||||
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
// Give Flutter, including plugins, an opportunity to handle window messages.
|
||||
if (flutter_controller_) {
|
||||
std::optional<LRESULT> result =
|
||||
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
|
||||
lparam);
|
||||
if (result) {
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
||||
switch (message) {
|
||||
case WM_FONTCHANGE:
|
||||
flutter_controller_->engine()->ReloadSystemFonts();
|
||||
break;
|
||||
}
|
||||
|
||||
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef RUNNER_FLUTTER_WINDOW_H_
|
||||
#define RUNNER_FLUTTER_WINDOW_H_
|
||||
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "win32_window.h"
|
||||
|
||||
// A window that does nothing but host a Flutter view.
|
||||
class FlutterWindow : public Win32Window {
|
||||
public:
|
||||
// Creates a new FlutterWindow hosting a Flutter view running |project|.
|
||||
explicit FlutterWindow(const flutter::DartProject& project);
|
||||
virtual ~FlutterWindow();
|
||||
|
||||
protected:
|
||||
// Win32Window:
|
||||
bool OnCreate() override;
|
||||
void OnDestroy() override;
|
||||
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept override;
|
||||
|
||||
private:
|
||||
// The project to run.
|
||||
flutter::DartProject project_;
|
||||
|
||||
// The Flutter instance hosted by this window.
|
||||
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
||||
};
|
||||
|
||||
#endif // RUNNER_FLUTTER_WINDOW_H_
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "flutter_window.h"
|
||||
#include "utils.h"
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||
// Attach to console when present (e.g., 'flutter run') or create a
|
||||
// new console when running with a debugger.
|
||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||
CreateAndAttachConsole();
|
||||
}
|
||||
|
||||
// Initialize COM, so that it is available for use in the library and/or
|
||||
// plugins.
|
||||
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
flutter::DartProject project(L"data");
|
||||
|
||||
std::vector<std::string> command_line_arguments =
|
||||
GetCommandLineArguments();
|
||||
|
||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||
|
||||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(1280, 720);
|
||||
if (!window.CreateAndShow(L"example", origin, size)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
window.SetQuitOnClose(true);
|
||||
|
||||
::MSG msg;
|
||||
while (::GetMessage(&msg, nullptr, 0, 0)) {
|
||||
::TranslateMessage(&msg);
|
||||
::DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
::CoUninitialize();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by Runner.rc
|
||||
//
|
||||
#define IDI_APP_ICON 101
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "utils.h"
|
||||
|
||||
#include <flutter_windows.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void CreateAndAttachConsole() {
|
||||
if (::AllocConsole()) {
|
||||
FILE *unused;
|
||||
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
|
||||
_dup2(_fileno(stdout), 1);
|
||||
}
|
||||
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
|
||||
_dup2(_fileno(stdout), 2);
|
||||
}
|
||||
std::ios::sync_with_stdio();
|
||||
FlutterDesktopResyncOutputStreams();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> GetCommandLineArguments() {
|
||||
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
|
||||
int argc;
|
||||
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
|
||||
if (argv == nullptr) {
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
|
||||
std::vector<std::string> command_line_arguments;
|
||||
|
||||
// Skip the first argument as it's the binary name.
|
||||
for (int i = 1; i < argc; i++) {
|
||||
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
|
||||
}
|
||||
|
||||
::LocalFree(argv);
|
||||
|
||||
return command_line_arguments;
|
||||
}
|
||||
|
||||
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
|
||||
if (utf16_string == nullptr) {
|
||||
return std::string();
|
||||
}
|
||||
int target_length = ::WideCharToMultiByte(
|
||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||
-1, nullptr, 0, nullptr, nullptr);
|
||||
if (target_length == 0) {
|
||||
return std::string();
|
||||
}
|
||||
std::string utf8_string;
|
||||
utf8_string.resize(target_length);
|
||||
int converted_length = ::WideCharToMultiByte(
|
||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||
-1, utf8_string.data(),
|
||||
target_length, nullptr, nullptr);
|
||||
if (converted_length == 0) {
|
||||
return std::string();
|
||||
}
|
||||
return utf8_string;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef RUNNER_UTILS_H_
|
||||
#define RUNNER_UTILS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Creates a console for the process, and redirects stdout and stderr to
|
||||
// it for both the runner and the Flutter library.
|
||||
void CreateAndAttachConsole();
|
||||
|
||||
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
|
||||
// encoded in UTF-8. Returns an empty std::string on failure.
|
||||
std::string Utf8FromUtf16(const wchar_t* utf16_string);
|
||||
|
||||
// Gets the command line arguments passed in as a std::vector<std::string>,
|
||||
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
|
||||
std::vector<std::string> GetCommandLineArguments();
|
||||
|
||||
#endif // RUNNER_UTILS_H_
|
||||
@@ -0,0 +1,245 @@
|
||||
#include "win32_window.h"
|
||||
|
||||
#include <flutter_windows.h>
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
||||
|
||||
// The number of Win32Window objects that currently exist.
|
||||
static int g_active_window_count = 0;
|
||||
|
||||
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
|
||||
|
||||
// Scale helper to convert logical scaler values to physical using passed in
|
||||
// scale factor
|
||||
int Scale(int source, double scale_factor) {
|
||||
return static_cast<int>(source * scale_factor);
|
||||
}
|
||||
|
||||
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
|
||||
// This API is only needed for PerMonitor V1 awareness mode.
|
||||
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
|
||||
HMODULE user32_module = LoadLibraryA("User32.dll");
|
||||
if (!user32_module) {
|
||||
return;
|
||||
}
|
||||
auto enable_non_client_dpi_scaling =
|
||||
reinterpret_cast<EnableNonClientDpiScaling*>(
|
||||
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
|
||||
if (enable_non_client_dpi_scaling != nullptr) {
|
||||
enable_non_client_dpi_scaling(hwnd);
|
||||
FreeLibrary(user32_module);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Manages the Win32Window's window class registration.
|
||||
class WindowClassRegistrar {
|
||||
public:
|
||||
~WindowClassRegistrar() = default;
|
||||
|
||||
// Returns the singleton registar instance.
|
||||
static WindowClassRegistrar* GetInstance() {
|
||||
if (!instance_) {
|
||||
instance_ = new WindowClassRegistrar();
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
// Returns the name of the window class, registering the class if it hasn't
|
||||
// previously been registered.
|
||||
const wchar_t* GetWindowClass();
|
||||
|
||||
// Unregisters the window class. Should only be called if there are no
|
||||
// instances of the window.
|
||||
void UnregisterWindowClass();
|
||||
|
||||
private:
|
||||
WindowClassRegistrar() = default;
|
||||
|
||||
static WindowClassRegistrar* instance_;
|
||||
|
||||
bool class_registered_ = false;
|
||||
};
|
||||
|
||||
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
|
||||
|
||||
const wchar_t* WindowClassRegistrar::GetWindowClass() {
|
||||
if (!class_registered_) {
|
||||
WNDCLASS window_class{};
|
||||
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
window_class.lpszClassName = kWindowClassName;
|
||||
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
||||
window_class.cbClsExtra = 0;
|
||||
window_class.cbWndExtra = 0;
|
||||
window_class.hInstance = GetModuleHandle(nullptr);
|
||||
window_class.hIcon =
|
||||
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
|
||||
window_class.hbrBackground = 0;
|
||||
window_class.lpszMenuName = nullptr;
|
||||
window_class.lpfnWndProc = Win32Window::WndProc;
|
||||
RegisterClass(&window_class);
|
||||
class_registered_ = true;
|
||||
}
|
||||
return kWindowClassName;
|
||||
}
|
||||
|
||||
void WindowClassRegistrar::UnregisterWindowClass() {
|
||||
UnregisterClass(kWindowClassName, nullptr);
|
||||
class_registered_ = false;
|
||||
}
|
||||
|
||||
Win32Window::Win32Window() {
|
||||
++g_active_window_count;
|
||||
}
|
||||
|
||||
Win32Window::~Win32Window() {
|
||||
--g_active_window_count;
|
||||
Destroy();
|
||||
}
|
||||
|
||||
bool Win32Window::CreateAndShow(const std::wstring& title,
|
||||
const Point& origin,
|
||||
const Size& size) {
|
||||
Destroy();
|
||||
|
||||
const wchar_t* window_class =
|
||||
WindowClassRegistrar::GetInstance()->GetWindowClass();
|
||||
|
||||
const POINT target_point = {static_cast<LONG>(origin.x),
|
||||
static_cast<LONG>(origin.y)};
|
||||
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
||||
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||||
double scale_factor = dpi / 96.0;
|
||||
|
||||
HWND window = CreateWindow(
|
||||
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
||||
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
||||
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
||||
|
||||
if (!window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return OnCreate();
|
||||
}
|
||||
|
||||
// static
|
||||
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
if (message == WM_NCCREATE) {
|
||||
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||||
SetWindowLongPtr(window, GWLP_USERDATA,
|
||||
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
|
||||
|
||||
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
|
||||
EnableFullDpiSupportIfAvailable(window);
|
||||
that->window_handle_ = window;
|
||||
} else if (Win32Window* that = GetThisFromHandle(window)) {
|
||||
return that->MessageHandler(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
return DefWindowProc(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
LRESULT
|
||||
Win32Window::MessageHandler(HWND hwnd,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
switch (message) {
|
||||
case WM_DESTROY:
|
||||
window_handle_ = nullptr;
|
||||
Destroy();
|
||||
if (quit_on_close_) {
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_DPICHANGED: {
|
||||
auto newRectSize = reinterpret_cast<RECT*>(lparam);
|
||||
LONG newWidth = newRectSize->right - newRectSize->left;
|
||||
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
||||
|
||||
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
||||
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
case WM_SIZE: {
|
||||
RECT rect = GetClientArea();
|
||||
if (child_content_ != nullptr) {
|
||||
// Size and position the child window.
|
||||
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
|
||||
rect.bottom - rect.top, TRUE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_ACTIVATE:
|
||||
if (child_content_ != nullptr) {
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DefWindowProc(window_handle_, message, wparam, lparam);
|
||||
}
|
||||
|
||||
void Win32Window::Destroy() {
|
||||
OnDestroy();
|
||||
|
||||
if (window_handle_) {
|
||||
DestroyWindow(window_handle_);
|
||||
window_handle_ = nullptr;
|
||||
}
|
||||
if (g_active_window_count == 0) {
|
||||
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
|
||||
}
|
||||
}
|
||||
|
||||
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
|
||||
return reinterpret_cast<Win32Window*>(
|
||||
GetWindowLongPtr(window, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
void Win32Window::SetChildContent(HWND content) {
|
||||
child_content_ = content;
|
||||
SetParent(content, window_handle_);
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
||||
frame.bottom - frame.top, true);
|
||||
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
|
||||
RECT Win32Window::GetClientArea() {
|
||||
RECT frame;
|
||||
GetClientRect(window_handle_, &frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
HWND Win32Window::GetHandle() {
|
||||
return window_handle_;
|
||||
}
|
||||
|
||||
void Win32Window::SetQuitOnClose(bool quit_on_close) {
|
||||
quit_on_close_ = quit_on_close;
|
||||
}
|
||||
|
||||
bool Win32Window::OnCreate() {
|
||||
// No-op; provided for subclasses.
|
||||
return true;
|
||||
}
|
||||
|
||||
void Win32Window::OnDestroy() {
|
||||
// No-op; provided for subclasses.
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
#ifndef RUNNER_WIN32_WINDOW_H_
|
||||
#define RUNNER_WIN32_WINDOW_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
|
||||
// inherited from by classes that wish to specialize with custom
|
||||
// rendering and input handling
|
||||
class Win32Window {
|
||||
public:
|
||||
struct Point {
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
|
||||
};
|
||||
|
||||
struct Size {
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
Size(unsigned int width, unsigned int height)
|
||||
: width(width), height(height) {}
|
||||
};
|
||||
|
||||
Win32Window();
|
||||
virtual ~Win32Window();
|
||||
|
||||
// Creates and shows a win32 window with |title| and position and size using
|
||||
// |origin| and |size|. New windows are created on the default monitor. Window
|
||||
// sizes are specified to the OS in physical pixels, hence to ensure a
|
||||
// consistent size to will treat the width height passed in to this function
|
||||
// as logical pixels and scale to appropriate for the default monitor. Returns
|
||||
// true if the window was created successfully.
|
||||
bool CreateAndShow(const std::wstring& title,
|
||||
const Point& origin,
|
||||
const Size& size);
|
||||
|
||||
// Release OS resources associated with window.
|
||||
void Destroy();
|
||||
|
||||
// Inserts |content| into the window tree.
|
||||
void SetChildContent(HWND content);
|
||||
|
||||
// Returns the backing Window handle to enable clients to set icon and other
|
||||
// window properties. Returns nullptr if the window has been destroyed.
|
||||
HWND GetHandle();
|
||||
|
||||
// If true, closing this window will quit the application.
|
||||
void SetQuitOnClose(bool quit_on_close);
|
||||
|
||||
// Return a RECT representing the bounds of the current client area.
|
||||
RECT GetClientArea();
|
||||
|
||||
protected:
|
||||
// Processes and route salient window messages for mouse handling,
|
||||
// size change and DPI. Delegates handling of these to member overloads that
|
||||
// inheriting classes can handle.
|
||||
virtual LRESULT MessageHandler(HWND window,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept;
|
||||
|
||||
// Called when CreateAndShow is called, allowing subclass window-related
|
||||
// setup. Subclasses should return false if setup fails.
|
||||
virtual bool OnCreate();
|
||||
|
||||
// Called when Destroy is called.
|
||||
virtual void OnDestroy();
|
||||
|
||||
private:
|
||||
friend class WindowClassRegistrar;
|
||||
|
||||
// OS callback called by message pump. Handles the WM_NCCREATE message which
|
||||
// is passed when the non-client area is being created and enables automatic
|
||||
// non-client DPI scaling so that the non-client area automatically
|
||||
// responsponds to changes in DPI. All other messages are handled by
|
||||
// MessageHandler.
|
||||
static LRESULT CALLBACK WndProc(HWND const window,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept;
|
||||
|
||||
// Retrieves a class instance pointer for |window|
|
||||
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
|
||||
|
||||
bool quit_on_close_ = false;
|
||||
|
||||
// window handle for top level window.
|
||||
HWND window_handle_ = nullptr;
|
||||
|
||||
// window handle for hosted content.
|
||||
HWND child_content_ = nullptr;
|
||||
};
|
||||
|
||||
#endif // RUNNER_WIN32_WINDOW_H_
|
||||
@@ -0,0 +1,25 @@
|
||||
# conditional_parent_builder
|
||||
|
||||
A widget that allows developers to conditionally wrap a child widget with a parent widget.
|
||||
This is useful in situations where a child widget should always be built, but should be wrapped
|
||||
by another widget if certain conditions are met.
|
||||
|
||||
In the following real-world example, we conditionally wrap the child widget
|
||||
tree with a context menu if the message is not deleted:
|
||||
```dart
|
||||
ConditionalParentBuilder(
|
||||
builder: (context, child) {
|
||||
if (!widget.message.isDeleted) {
|
||||
return ContextMenuArea(
|
||||
builder: (context) => buildContextMenu(),
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
},
|
||||
child: Material(...),
|
||||
),
|
||||
```
|
||||
This example can be found in the `stream_chat_flutter` source code under
|
||||
`src/message_widget/message_widget.dart`.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
library conditional_parent_builder;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@template parentBuilder}
|
||||
/// A function that provides the [BuildContext] and the [child] widget.
|
||||
/// {@endtemplate}
|
||||
typedef ParentBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
Widget child,
|
||||
);
|
||||
|
||||
/// {@template conditionalParentBuilder}
|
||||
/// A widget that allows developers to conditionally wrap the [child] widget
|
||||
/// with a parent widget.
|
||||
///
|
||||
/// In the following real-world example, we conditionally wrap the child widget
|
||||
/// tree with a context menu if the message is not deleted:
|
||||
/// ```dart
|
||||
/// ConditionalParentBuilder(
|
||||
/// builder: (context, child) {
|
||||
/// if (!widget.message.isDeleted) {
|
||||
/// return ContextMenuArea(
|
||||
/// builder: (context) => buildContextMenu(),
|
||||
/// child: child,
|
||||
/// );
|
||||
/// } else {
|
||||
/// return child;
|
||||
/// }
|
||||
/// },
|
||||
/// child: Material(...),
|
||||
/// ),
|
||||
/// ```
|
||||
/// This example can be found in the `stream_chat_flutter` source code under
|
||||
/// `src/message_widget/message_widget.dart`.
|
||||
/// {@endtemplate}
|
||||
class ConditionalParentBuilder extends StatelessWidget {
|
||||
/// {@macro conditionalParentBuilder}
|
||||
const ConditionalParentBuilder({
|
||||
super.key,
|
||||
required this.builder,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
/// {@macro parentBuilder}
|
||||
final ParentBuilder builder;
|
||||
|
||||
/// The child widget to build.
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return builder.call(context, child);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# platform_widget_builder
|
||||
|
||||
This package is a more specialized version of the [flutter_platform_widgets](https://pub.dev/packages/flutter_platform_widgets) package.
|
||||
It provides two specialized widget builders:
|
||||
* `PlatformWidgetBuilder`
|
||||
* `DesktopWidgetBuilder`
|
||||
|
||||
### `PlatformWidgetBuilder`
|
||||
This widget differs from the `PlatformWidgetBuilder` found in the `flutter_platform_widgets` package in that it
|
||||
provides three builders:
|
||||
* `mobile`
|
||||
* `desktop`
|
||||
* `web`
|
||||
|
||||
This allows developers to build different widgets for their generalized platform targets
|
||||
(Android + iOS = mobile, macOS + Windows + Linux = desktop). This is advantageous when requirements call for one set
|
||||
of widgets that are identical across mobile, a different set of widgets that are identical across desktop, and another
|
||||
that are required for web.
|
||||
|
||||
### `DesktopWidgetBuilder`
|
||||
This widget is more specialized than `PlatformWidgetBuilder` in that it allows for more targeted widget-building for
|
||||
desktop platforms. It provides three builders:
|
||||
* `macOS`
|
||||
* `windows`
|
||||
* `linux`
|
||||
|
||||
This allows developers to build different widgets for the various desktop platforms. This is advantageous when building
|
||||
native-looking desktop applications using the `macos_ui`, `fluent_ui`, and `yaru_widgets` packages.
|
||||
@@ -0,0 +1,4 @@
|
||||
library platform_widget_builder;
|
||||
|
||||
export 'src/desktop_widget_builder.dart';
|
||||
export 'src/platform_widget_builder.dart';
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/platform_widget_builder/src/desktop_widget_base.dart';
|
||||
|
||||
/// A widget that will only be built for the specified desktop Platforms.
|
||||
///
|
||||
/// See [DesktopWidgetBuilder] and [DesktopWidgetBase] for more.
|
||||
///
|
||||
/// Also see: [PlatformWidget] and [PlatformWidgetBase].
|
||||
class DesktopWidget extends DesktopWidgetBase<Widget, Widget, Widget> {
|
||||
/// Builds a [DesktopWidget].
|
||||
const DesktopWidget({
|
||||
super.key,
|
||||
this.macOS,
|
||||
this.windows,
|
||||
this.linux,
|
||||
});
|
||||
|
||||
/// The widget to build for macOS.
|
||||
final PlatformBuilder<Widget?>? macOS;
|
||||
|
||||
/// The widget to build for Windows.
|
||||
final PlatformBuilder<Widget?>? windows;
|
||||
|
||||
/// The widget to build for Linux.
|
||||
final PlatformBuilder<Widget?>? linux;
|
||||
|
||||
@override
|
||||
Widget createMacosWidget(BuildContext context) =>
|
||||
macOS?.call(context) ?? const SizedBox.shrink();
|
||||
|
||||
@override
|
||||
Widget createWindowsWidget(BuildContext context) =>
|
||||
windows?.call(context) ?? const SizedBox.shrink();
|
||||
|
||||
@override
|
||||
Widget createLinuxWidget(BuildContext context) =>
|
||||
linux?.call(context) ?? const SizedBox.shrink();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' show Theme;
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// A generic widget builder function.
|
||||
typedef PlatformBuilder<T> = T Function(
|
||||
BuildContext context,
|
||||
);
|
||||
|
||||
/// An abstract class used as a building block for creating
|
||||
/// [DesktopPlatformWidget]s.
|
||||
///
|
||||
/// This class is similar to [PlatformWidgetBase], which broadly covers
|
||||
/// the platform categories; it combines the desktop platforms into a single
|
||||
/// "desktop" target, for the purpose of returning the same widget one time
|
||||
/// for all platforms in that category. [DesktopWidgetBase] differs in that
|
||||
/// it more specifically targets each platform in the "desktop" category.
|
||||
///
|
||||
/// This class utilizes generics to define the types of widgets it expects to
|
||||
/// build:
|
||||
/// * M = macOS
|
||||
/// * W = Windows
|
||||
/// * L = Linux
|
||||
abstract class DesktopWidgetBase<M extends Widget, W extends Widget,
|
||||
L extends Widget> extends StatelessWidget {
|
||||
/// Builds a [DesktopWidgetBase].
|
||||
const DesktopWidgetBase({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final platform = Theme.of(context).platform;
|
||||
if (platform == TargetPlatform.macOS) {
|
||||
return createMacosWidget(context);
|
||||
} else if (platform == TargetPlatform.windows) {
|
||||
return createWindowsWidget(context);
|
||||
} else if (platform == TargetPlatform.linux) {
|
||||
return createLinuxWidget(context);
|
||||
}
|
||||
|
||||
return throw UnsupportedError(
|
||||
'This platform is not supported: $defaultTargetPlatform',
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds a `M` macOS widget.
|
||||
M createMacosWidget(BuildContext context);
|
||||
|
||||
/// Builds a `W` Windows widget.
|
||||
W createWindowsWidget(BuildContext context);
|
||||
|
||||
/// Builds a `L` Linux widget.
|
||||
L createLinuxWidget(BuildContext context);
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/platform_widget_builder/src/desktop_widget.dart';
|
||||
|
||||
/// A widget-building function that includes the child widget.
|
||||
typedef DesktopTargetBuilder = Widget? Function(
|
||||
BuildContext context,
|
||||
Widget? child,
|
||||
)?;
|
||||
|
||||
/// A widget that utilizes [DesktopWidgetBuilder]s to build different widgets
|
||||
/// for each specified desktop platform.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```
|
||||
/// DesktopWidgetBuilder(
|
||||
/// macOS: (context, child) => MacosWidget(),
|
||||
/// windows: (context, child) => WindowsWidget(),
|
||||
/// linux: (context, child) => LinuxWidget(),
|
||||
/// ),
|
||||
/// ```
|
||||
class DesktopWidgetBuilder extends StatelessWidget {
|
||||
/// Builds a [DesktopWidgetBuilder].
|
||||
const DesktopWidgetBuilder({
|
||||
super.key,
|
||||
this.child,
|
||||
this.macOS,
|
||||
this.windows,
|
||||
this.linux,
|
||||
});
|
||||
|
||||
/// The child widget.
|
||||
final Widget? child;
|
||||
|
||||
/// The widget to build for macOS.
|
||||
final DesktopTargetBuilder? macOS;
|
||||
|
||||
/// The widget to build for windows.
|
||||
final DesktopTargetBuilder? windows;
|
||||
|
||||
/// The widget to build for linux.
|
||||
final DesktopTargetBuilder? linux;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DesktopWidget(
|
||||
macOS: (context) => macOS?.call(context, child),
|
||||
windows: (context) => windows?.call(context, child),
|
||||
linux: (context) => linux?.call(context, child),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget_base.dart';
|
||||
|
||||
/// A widget that will only be built for the specific Platforms:
|
||||
///
|
||||
/// See [PlatformWidgetBuilder] and [PlatformWidgetBase] for more.
|
||||
///
|
||||
/// Also see: [DesktopWidget] and [DesktopWidgetBase].
|
||||
class PlatformWidget extends PlatformWidgetBase<Widget, Widget, Widget> {
|
||||
/// Builds a [PlatformWidget].
|
||||
const PlatformWidget({
|
||||
super.key,
|
||||
this.desktop,
|
||||
this.mobile,
|
||||
this.web,
|
||||
});
|
||||
|
||||
/// The mobile widget to build.
|
||||
final PlatformBuilder<Widget?>? mobile;
|
||||
|
||||
/// The desktop widget to build.
|
||||
final PlatformBuilder<Widget?>? desktop;
|
||||
|
||||
/// The web widget to build.
|
||||
final PlatformBuilder<Widget?>? web;
|
||||
|
||||
@override
|
||||
Widget createDesktopWidget(BuildContext context) =>
|
||||
desktop?.call(context) ?? const SizedBox.shrink();
|
||||
|
||||
@override
|
||||
Widget createMobileWidget(BuildContext context) =>
|
||||
mobile?.call(context) ?? const SizedBox.shrink();
|
||||
|
||||
@override
|
||||
Widget createWebWidget(BuildContext context) =>
|
||||
web?.call(context) ?? const SizedBox.shrink();
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart' show Theme;
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// A generic widget builder function.
|
||||
typedef PlatformBuilder<T> = T Function(
|
||||
BuildContext context,
|
||||
);
|
||||
|
||||
/// An abstract class used as a building block for creating [PlatformWidget]s.
|
||||
///
|
||||
/// This class broadly covers the platforms by combining Android and iOS
|
||||
/// together as a "mobile" category, macOS, Windows, and Linux together as a
|
||||
/// "desktop" category. This is useful is cases where a widget is expected to
|
||||
/// be the same for the various mobile and desktop categories, and would
|
||||
/// therefore be tedious to return the same widget more than once for the
|
||||
/// specified category. This is unlike [DesktopWidgetBase], which more
|
||||
/// specifically targets the various platforms in the "desktop" category.
|
||||
///
|
||||
/// This class utilizes generics to define the types of widgets it expects to
|
||||
/// build:
|
||||
/// * M = Mobile
|
||||
/// * D = Desktop
|
||||
/// * W = Web
|
||||
abstract class PlatformWidgetBase<M extends Widget, D extends Widget,
|
||||
W extends Widget> extends StatelessWidget {
|
||||
/// Builds a [PlatformWidgetBase].
|
||||
const PlatformWidgetBase({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final platform = Theme.of(context).platform;
|
||||
if (platform == TargetPlatform.android || platform == TargetPlatform.iOS) {
|
||||
return createMobileWidget(context);
|
||||
} else if (platform == TargetPlatform.macOS ||
|
||||
platform == TargetPlatform.windows ||
|
||||
platform == TargetPlatform.linux) {
|
||||
return createDesktopWidget(context);
|
||||
} else {
|
||||
return createWebWidget(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a `M` mobile widget.
|
||||
M createMobileWidget(BuildContext context);
|
||||
|
||||
/// Builds a `D` desktop widget.
|
||||
D createDesktopWidget(BuildContext context);
|
||||
|
||||
/// Builds a `W` web widget.
|
||||
W createWebWidget(BuildContext context);
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget.dart';
|
||||
|
||||
/// A widget-building function that includes the child widget.
|
||||
typedef PlatformTargetBuilder = Widget? Function(
|
||||
BuildContext context,
|
||||
Widget? child,
|
||||
)?;
|
||||
|
||||
/// A widget that utilizes [PlatformTargetBuilder]s to build different widgets
|
||||
/// for each specified platform.
|
||||
///
|
||||
/// In the following real-world example, only the `mobile` builder is used
|
||||
/// to ensure the child widget is only built for Android and iOS:
|
||||
/// ```
|
||||
/// PlatformWidgetBuilder(
|
||||
/// mobile: (context, child) => _buildFilePickerSection(),
|
||||
/// ),
|
||||
/// ```
|
||||
class PlatformWidgetBuilder extends StatelessWidget {
|
||||
/// Builds a [PlatformWidgetBuilder].
|
||||
const PlatformWidgetBuilder({
|
||||
super.key,
|
||||
this.child,
|
||||
this.mobile,
|
||||
this.desktop,
|
||||
this.web,
|
||||
});
|
||||
|
||||
/// The child widget.
|
||||
final Widget? child;
|
||||
|
||||
/// The widget to build for mobile platforms.
|
||||
final PlatformTargetBuilder? mobile;
|
||||
|
||||
/// The widget to build for desktop platforms.
|
||||
final PlatformTargetBuilder? desktop;
|
||||
|
||||
/// The widget to build for web platforms.
|
||||
final PlatformTargetBuilder? web;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PlatformWidget(
|
||||
desktop: (context) => desktop?.call(context, child),
|
||||
mobile: (context) => mobile?.call(context, child),
|
||||
web: (context) => web?.call(context, child),
|
||||
);
|
||||
}
|
||||
}
|
||||
-1
@@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/item_positions_notifier.dart';
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/scrollable_positioned_list.dart';
|
||||
|
||||
|
||||
-1
@@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/item_positions_listener.dart';
|
||||
|
||||
/// Internal implementation of [ItemPositionsListener].
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/element_registry.dart';
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/indexed_key.dart';
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/item_positions_listener.dart';
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/viewport.dart';
|
||||
|
||||
/// {@template custom_scroll_view}
|
||||
|
||||
-1
@@ -8,7 +8,6 @@ import 'dart:math';
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/item_positions_listener.dart';
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/item_positions_notifier.dart';
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/positioned_list.dart';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user